Introspect number of arguments of a Function object?

153 views
Skip to first unread message

Jeff Ward

unread,
Aug 28, 2015, 3:42:09 PM8/28/15
to Haxe
Can function types (e.g. Int->Void) be passed as a Dynamic parameter? If so, is it possible to introspect the number of arguments the function takes?

I'm building a quick pub/sub utility, and I want the callback functions to optionally take a data parameter.

In AS3 and JS, function objects have a length parameter that reports the number of parameters they accept:

> var f = function(a, b) { return a+b;}
> f.length
2

So is it possible for a subscribe function to accept functions of varying signature:

function subscribe(channel:String, callback:Dynamic):Void
{
  var num_params = callback.length;
}
subscribe("foo", function() { trace("I don't take parameters"); }
subscribe("bar", function(msg:Dynamic) { trace("I do take parameters: "+msg); }

Or do I have to write two different subscribe functions:

function subscribe_none(channel:String, callback:Void->Void):Void
function subscribe_one(channel:String, callback:Dynamic->Void):Void

Thanks,
-Jeff

Mark Knol

unread,
Aug 28, 2015, 6:00:36 PM8/28/15
to Haxe
You can make the callback optional

subscribe(msg:String, ?callback:Dynamic->Void);

You can also use this to type instead of using Dynamic

subscribe<T>(msg:String, callback:T->Void);

You could optimize this, by marking the function with the @:generic metadata, then the output generates all used combinations. For JavaScript target it's not needed to add this metadata.

Jeff Ward

unread,
Aug 28, 2015, 6:43:41 PM8/28/15
to Haxe
> You can make the callback optional

I always want a callback (there's no point in subscribing without a callback), but I want the callback function to optionally take 0 or 1 arguments.

> You can also use this to type instead of using Dynamic

> subscribe<T>(msg:String, callback:T->Void);


Ah, now this is interesting if T can be Void. Who wants to bet that Void isn't a supported T parameter (somehow I always manage to hit the caveats and corner cases in Haxe's special sauce.)

I'll try it...

Nicolas Cannasse

unread,
Aug 29, 2015, 3:28:20 AM8/29/15
to haxe...@googlegroups.com
Le 28/08/2015 21:42, Jeff Ward a écrit :
> Can function types (e.g. Int->Void) be passed as a Dynamic parameter? If
> so, is it possible to introspect the number of arguments the function takes?

Not in a crossplatform manner, but you can use .length in JS and $nargs
in Neko (and maybe CPP?)

Best,
Nicolas

Alexander Kuzmenko

unread,
Aug 29, 2015, 4:18:10 AM8/29/15
to Haxe
You can use haxe.Function abstract to accept any callback signature.
But then you need platform specific code to find amount of arguments.

пятница, 28 августа 2015 г., 22:42:09 UTC+3 пользователь Jeff Ward написал:

Hugh

unread,
Aug 31, 2015, 1:58:27 AM8/31/15
to Haxe
There is a hidden int __ArgCount member on hxcpp objects, eg
   static function func(x:Int)
   
{
      trace
( untyped  __cpp__("{0}->__ArgCount()", func ) );
   
}

Hugh

Juraj Kirchheim

unread,
Aug 31, 2015, 5:04:58 AM8/31/15
to haxe...@googlegroups.com
On Fri, Aug 28, 2015 at 9:42 PM, Jeff Ward <jeff...@gmail.com> wrote:
Can function types (e.g. Int->Void) be passed as a Dynamic parameter?

Yes, but in general it is a very, very, very, (years later), very, bad idea to pass anything as `Dynamic`.
 
If so, is it possible to introspect the number of arguments the function takes?

If you define a function type, then yes, it is possible:

 
I'm building a quick pub/sub utility, and I want the callback functions to optionally take a data parameter.

That of course is a very different matter. In that case introspection of the callback's argument link is simply a bad approach.
 
In AS3 and JS, function objects have a length parameter that reports the number of parameters they accept:

> var f = function(a, b) { return a+b;}
> f.length
2

So is it possible for a subscribe function to accept functions of varying signature:

function subscribe(channel:String, callback:Dynamic):Void
{
  var num_params = callback.length;
}
subscribe("foo", function() { trace("I don't take parameters"); }
subscribe("bar", function(msg:Dynamic) { trace("I do take parameters: "+msg); }

Or do I have to write two different subscribe functions:

function subscribe_none(channel:String, callback:Void->Void):Void
function subscribe_one(channel:String, callback:Dynamic->Void):Void

No, you don't. Just define *a proper type to express what you mean*, such as `tink.core.Callback<T>` which can be created through `T->Void` or `Void->Void` to solve that specific problem: https://github.com/haxetink/tink_core#callback

Moreover, I really think you should get rid of that stringly typed API. Here's a sketch:

  abstract EventType<T>(String) {
    public inline function new(s) this = s;
  }

  interface Hub {
    public function publish<T>(event:EventType<T>, data:T):Void;
    public function subscribe(event:EventType<T>, handler:Callback<T>):CallbackLink;
  }

  class Toasting {
     static inline var DONE = new EventType<Array<Toast>>("Toasting::DONE");
  }

  myHub.subscribe(Toasting.DONE, function () trace("toasting done!"));
  myHub.subscribe(Toasting.DONE, function (toasts) trace("congratulation, you can now have ${toasts.length} toast"));
  myHub.subscribe(Toasting.DONE, function (toast:Toast) trace("my toast: $toast"));//doesn't compile for good reasons

This is more robust by far. It does everything you want, without resorting to `Dynamic` once in a user facing API.

Best,
Juraj

Jeff Ward

unread,
Aug 31, 2015, 12:34:48 PM8/31/15
to Haxe
haxe.Function abstract to accept any callback signature

Fantastic, thanks!

you can use .length in JS and $nargs in Neko (and maybe CPP?) 

There is a hidden int __ArgCount member on hxcpp objects

Excellent, thanks!

http://try.haxe.org/#0d158

Thanks for working out this sample.
 
 I really think you should get rid of that stringly typed API. Here's a sketch: 
 
  abstract EventType<T>(String) {
    public inline function new(s) this = s;
  }

  interface Hub {
    public function publish<T>(event:EventType<T>, data:T):Void;
    public function subscribe(event:EventType<T>, handler:Callback<T>):CallbackLink;
  }

  class Toasting {
     static inline var DONE = new EventType<Array<Toast>>("Toasting::DONE");
  }

This is a nice type safe design, and it exactly illustrates the difference of opinion between strict type thinking and dynamic type thinking. The downsides of strict typing are very subtle -- the amount of headspace required in the user's brain before (s)he can use it, an extraneous class to hold definitions, more imports, the dependencies created by these definitions...

The dynamic mindset gives you more than enough rope to hang yourself, plus an easy to use pre-tied noose. You're welcome to hang yourself, but I expect you'll use it appropriately. =)

Anyway, I do appreciate the input, Juraj, you're incredibly helpful and thorough.

Jeff Ward

unread,
Aug 31, 2015, 7:00:26 PM8/31/15
to Haxe
Hmm, type not found: haxe.Function 


Even though std/hx/Constraints.hx line 31-32 seems to define it:

@:callable
abstract Function(Dynamic) { }

Andreas Mokros

unread,
Aug 31, 2015, 7:16:20 PM8/31/15
to haxe...@googlegroups.com
Hi.
It's in the haxe.Constraints module, so:
haxe.Constraints.Function

--
Mockey

Jeff Ward

unread,
Aug 31, 2015, 8:12:24 PM8/31/15
to Haxe
It's in the haxe.Constraints module

Wow -- Haxe should win an award as most confusing language ever, even when you have the docs open in front of you. Well, thanks for the tip.

Jeff Ward

unread,
Sep 1, 2015, 12:15:45 AM9/1/15
to Haxe
Interesting, once the type was satisfied with haxe.Constraints.Function, I had to use $nargs in neko, but cpp didn't complain simply calling the functions with too many parameters (call always has one arg, handler function takes one or zero parameters):

#if neko
  if (untyped $nargs(tpair.handler)==0) {
    tpair.handler();
  } else {
    tpair.handler(tpair.data);
  }
#elseif cpp
  tpair.handler(tpair.data);
#end

But I went ahead and implemented ArgCount anyway, still works:

#elseif cpp
        if (untyped  __cpp__("{0}->__ArgCount()", tpair.handler )==0) {
          tpair.handler();
        } else {
          tpair.handler(tpair.data);
        }
#end

Neko and cpp are my targets, so I'm set.

Hugh

unread,
Sep 2, 2015, 12:40:46 AM9/2/15
to Haxe
To give fair warning, I reserve the right to change this (__ArgCount) to something else (possibly double up with __length to save space) in the future.

Hugh

Jeff Ward

unread,
Sep 2, 2015, 10:26:34 AM9/2/15
to Haxe

To give fair warning, I reserve the right to change this (__ArgCount) to something else (possibly double up with __length to save space) in the future.
 
Fair enough! Maybe I'll just remove the check since cpp seemed to work fine without it!
Reply all
Reply to author
Forward
0 new messages