Return function of specific (but variable) arity from fn factory?

Get help with using AutoHotkey (v2 or newer) and its commands and hotkeys
sirksel
Posts: 222
Joined: 12 Nov 2013, 23:48

Return function of specific (but variable) arity from fn factory?

Post by sirksel » 10 Oct 2021, 20:02

Is there a way to return a function from a function factory of specific non-variadic arity, which arity is passed to the outer function?

In other words, if the outer function were called as outer(3, fx),
it would return a function object such as (v1,v2,v3)=>(somecalc + fx(v1,v2,v3)).

If it were called as outer(1, fy),
it would return (v1)=>(somecalc + fy(v1)).

User avatar
kczx3
Posts: 1640
Joined: 06 Oct 2015, 21:39

Re: Return function of specific (but variable) arity from fn factory?

Post by kczx3 » 10 Oct 2021, 20:23

You’d probably have to code each length of arity? Or just accept variadic and handle the unhandled cases in fx

lexikos
Posts: 9569
Joined: 30 Sep 2013, 04:07
Contact:

Re: Return function of specific (but variable) arity from fn factory?

Post by lexikos » 12 Oct 2021, 07:07

What's the difference between (v1)=>(somecalc + fy(v1)) and (v*)=>(somecalc + fy(v*)), if fy requires exactly 1 parameter either way?

Or in other words, how do you define or observe arity?

I suppose that for r := outer(3, fx) and (v1)=>,
r(a, b) would throw an exception at the outer call (the anonymous function) instead of the inner call (fy).
r.MinParams and r.MaxParams would be 1 instead of 0.
r.IsVariadic would be false instead of true.

You cannot set the MinParams, MaxParams and IsVariadic properties, but you can redefine them for a given function or closure instance. This would only affect what value is returned when the property is queried.

For (re)implementing parameter constraints, you need only produce a function which performs some checks before calling the real function.

swagfag
Posts: 6222
Joined: 11 Jan 2017, 17:59

Re: Return function of specific (but variable) arity from fn factory?

Post by swagfag » 13 Oct 2021, 22:11

lexikos wrote:
12 Oct 2021, 07:07
You cannot set the MinParams, MaxParams and IsVariadic properties, but you can redefine them for a given function or closure instance. This would only affect what value is returned when the property is queried.
i think it would be very useful if u actually could set those properties. for example, suppose the hypothetical Func::BindWithArgCount(cRequiredArgs, cOptionalArgs, bIsVariadic, ArgsToBind*) existed, his code would become:

Code: Select all

#Requires AutoHotkey v2.0-beta.2-optimisticallylol

outer(arity, someFunction) => (inner(v*) => (somecalc + someFunction(v*))).BindWithArgCount(arity, 0, false)
                                                                                            ^^^^^ - 3
                                                                                                   ^ - no optional params
                                                                                                      ^^^^^ - not variadic
                                                                                                           ^ - and no additional args to bind
and another use-case where this might be useful is for creating DllCall wrappers(and other stuff, defining Prototypes). right now u can do something like:

Code: Select all

#Requires AutoHotkey v2.0-beta.1
MessageBox := DllCall.Bind('MessageBox', 'Ptr', , 'Str', , 'Str', , 'UInt')
which would work just fine when used as originally intended:

Code: Select all

MessageBox(A_ScriptHwnd, 'msgbox text', 'msgbox title', 0)
but what if someone decided to do this instead(eg because they didnt know any better/ whatever reasons):

Code: Select all

; VS how it might end up being (inadvertently, too) used with no apparent errors to show
MessageBox(A_ScriptHwnd, 'msgbox text', 'msgbox title', 0, 'Str', 'a', 'Str', 'bunch', 'Str', 'of', 'Str', 'garbage')
the code may still work. or it may not. or it may eventually not. in any case, theres nothing to restrict the parameter count they should have supplied(please dont say "well they should have read the docs, duh")

u could write something like this instead:

Code: Select all

MessageBox(hwnd, title, caption, dwFlags) => DllCall('MessageBox', 'Ptr', hwnd, 'Str', title, 'Str', caption, 'UInt', dwFlags)
now the parameter count is enforced. and ure also incurring a parameter passing overhead on each call, yaaay......
or u could stick a custom parameter-checking/enforcing function before the DllCall pretty much to the same effect

or u could write the hypothetical:

Code: Select all

MessageBox := DllCall.BindWithArgCount(4, 0, false, 'MessageBox', 'Ptr', , 'Str', , 'Str', , 'UInt')
param count enforced, and no (perceptible) slowdown, since the param-checking would now be done by the c++ code

lexikos
Posts: 9569
Joined: 30 Sep 2013, 04:07
Contact:

Re: Return function of specific (but variable) arity from fn factory?

Post by lexikos » 17 Oct 2021, 01:53

swagfag wrote:
13 Oct 2021, 22:11
i think it would be very useful if u actually could set those properties.
I nearly explained in my previous post how you can set those properties (via NumPut) and why doing so would be bad (because they either won't be used or will incorrectly change the behaviour of the function). Actually, I wrote about it and then deleted it before posting.

Instead, I pointed out how anyone can trivially redefine the properties and implement arbitrary parameter constraints. There is no need for a hypothetical BindWithArgCount method, because defining an actual method is trivial.
and another use-case where this might be useful is for creating DllCall wrappers
In any given set of APIs, there are generally some functions which benefit from additional wrapping, due to different string and memory allocation conventions, structs, error-reporting conventions, enum and flag parameters, etc. There are many more errors that could be prevented by adding error checking in a wrapper function, beyond just validating the number of parameters. DllCall.Bind can only take you so far (binding to user-defined functions can take you the rest of the way).
and ure also incurring a parameter passing overhead on each call, yaaay......
It is not productive to avoid creating functions based on misplaced concern over negligible overhead.

There are more important issues, such as the inability to identify bound functions in stack traces, or time lost while trying to trace cryptic error messages.

sirksel
Posts: 222
Joined: 12 Nov 2013, 23:48

Re: Return function of specific (but variable) arity from fn factory?

Post by sirksel » 20 Oct 2021, 23:30

Sorry I'm a little bit late in the discussion. In answer to @lexikos' question:
What's the difference between (v1)=>(somecalc + fy(v1)) and (v*)=>(somecalc + fy(v*)), if fy requires exactly 1 parameter either way?
The particular functional programming use case I'm trying to handle is, for example, a "not" function factory:

Code: Select all

notff(f) => x => !f(x)   ;not function factory
iseven(x) => mod(x,2) == 0
isodd := notff(iseven)   ;works great on a 1-arity function
eq(x,y) => x==y   ;implementation in actual methods differs by object
neq := notff(eq)   ;defined in a lower-level base, but needs 2-arity notff
The wrapper functions are generally more complex than a single !, they are often implemented as methods rather than functions, and there are very many of them. Options I've considered:
  • I could have a 1-, 2- and 3-arity (etc) version of notff or other wrappers, but there are a lot of these functions.
  • I could use an if statement inside the wrapper to return the correct arity, but I'd be doing this handling in every single wrapper function factory, which seemed duplicative. This is why I was trying to find a generic way to wrap a function returning specified arity... a la my original outer(3, fx) question
  • I can easily (and currently do) use notff(f) => (p*) => !f(p*) but, I Iose visibility to min/max params when calling the derived function... unless I instance-override those props. I do have functions that need to check these, so this is what I generally end up doing. To overload min/max/variadic etc. seems to be a lot of assignment for a functional approach that's supposed to make things simpler. I guess I could handle those assignments in a universal way that doesn't need to be repeated in each wrapper.
  • I could try to be more true to functional programming style and make all functions single-arity. With ahk's great support for optional arguments, I was trying to use a hybrid approach in some places (since I'm already using objects, which aren't themselves pure-play functional programming constructs)... but optional arguments require wrapping multiple-arity function varieties.
  • On call, I could maybe introspect "through" the wrapper functions until a non-wrapper is discovered, in order to learn the true underlying arity. I'm not sure that's even possible, nor might it be straightforward when the transform done by the wrapper isn't simple. Also, seems better to be doing the heavy lifting when defining the functions rather than when calling them.
From the discussion so far, I think you both already understood what I was looking for in my quixotic quest for a quasi-functional AHK style. Anyhow, I'm currently handling these situations using the bullet #3 approach above. If you've got any better ideas, please let me know. I've still got 30K or more lines to go in my refactor... partially because I keep changing my mind on approaches such as these... :) Thanks again for all the discussion and help!

Post Reply

Return to “Ask for Help (v2)”