COMMUNITY

Would appreciate help with type parameters

i am trying to make a simple event class

class Event<T> {
    public var subscribers : Array<T -> Void>;

    public function subscribe(f : T -> Void) {
        subscribers.push(f);
    }

    public function new() {
        subscribers = new Array<T -> Void>();
    }
}

and when i try to use it like this

var event = new Event<Void>();
event.subscribe(function() { trace("!"); });

i get

Void -> Void should be (Void) -> Void

is it even possible to use Void as type parameter in that way? it works with any non-Void type i tried it with, but it would be nice to use this event class as notifier without any data too.

EDIT:
i used “blockquote” instead of “Preformatted text”, so some haven’t been visible

Hi! It is not possible to pass a function with no arguments, because it has different arity than required, and there’s no unit type in Haxe, so functions without arguments have zero arity.

There are a couple ways around it:

  • add your own “unit type” and use it instead of Void, e.g.:
    enum abstract NoData(Dynamic) {
      var NoData = null; // just a simple null value at run-time
    }
    
    then you can have Event<NoData>, callbacks that take NoData and dispatch(NoData)
  • use an abstract for your callbacks with an implicit conversion from a “niladic” function
    abstract EventCallback<T>(T->Void) from T->Void {
      @:from static inline function fromNiladic<T>(f:()->Void):EventCallback<T> {
        return _ -> f(); // could be just `cast f` for the targets that don't care like JS
      }
    }
    

You can also combine them and make fromNiladic return EventCallback<NoData> if you want to forbid passing no-argument functions for anything else than EventCallback<NoData>.

Welcome to the forum!

Some things I notice:

  • I think your class needs to be parameterized too: class Event<T> { ..
  • You could give the T a name, by doing so: (data:T) -> Void, that’ll make your callbacks more descriptive.
  • You could also make typedef for this callback, that avoids repeating T -> Void over again.
class Event<T> {
  public var subscribers : Array<EventCallBack<T>>;

  public function subscribe(f : EventCallBack<T>) {
    subscribers.push(f);
  }

  public function new() {
    subscribers = new Array<EventCallBack<T>>();
  }
}

private typedef EventCallBack<T> = (data:T) -> Void;

Now back to your example, you don’t provide a event value to your subscribe function, so this should work:

var event = new Event();
event.subscribe(function(data) { trace(data + "!"); });

If you don’t even want to have data, then the EventCallBack could look like this:

private typedef EventCallBack<T> = () -> Void;

Using older syntax that is Void -> Void, but I hope you’re on Haxe 4 too

Another option is to generalize type parameter even more:

class Event<T> {
	public function subscribe(f : T) {
		subscribers.push(f);
	}

	public function new() {
		subscribers = new Array<T>();
	}
}

//and then
var event = new Event();
event.subscribe(function() { trace("!"); }); // Ok!
$type(event); // Event<Void->Void>

You can add haxe.Constraints.Function constraint to limit allowed type parameters to functions only.