Invocations are better partial functions for Ruby, because sometimes
Proc#curry just isn’t enough.
Partial function evaluation is a very useful tool, but understanding the state
carried by a curried Proc is often cumbersome and unintuitive.
Invocations provides a single class: the Invocation. It functions as a
drop-in alternative to a Proc, with a few notable improvements (none of
which break compatibility):
- An
Invocationis implicitly self-currying. Which is to say that if you call it without all the parameters it requires, it simply returns anInvocationthat requires the missing parameters. - An
Invocationallows non-keyword arguments to be given as keywords. - An
Invocationallows arguments to be given out of order, as keyword arguments. - An
Invocationhas several methods for inspection, including ones for listing the missing arguments, identifying keyword inferences, explaining how the underlying function will be called, and reporting internal state.
gem install invocationsAn Invocation is a drop-in alternative to Proc, or lambda. You can use
it as an explicit &block, etc. As a result, it’s very easy to adapt existing
code to use it.
Let’s lay out a simple function that will serve as our example, going forward.
This power function takes two arguments, n (a number) and e (an
exponent) and returns the result of raising n to e:
lambda_power = lambda { |n, e| n ** e }Now, this is somewhat contrived, because I’ve deliberately defined the arguments in the least convenient order, for illustrative purposes.
The equivalent Invocation would be:
invoke_power = Invocation.new { |n, e| n ** e }Invocations includes an optional Refinement for that brings the syntax
more in line with proc and lambda:
using Invocations
invoke_power = invocation { |n, e| n ** e }Calling either of these is the same:
lambda_power.(5, 2) #=> 25
invoke_power.(5, 2) #=> 25Let’s say we wanted to define lambda_square and lambda_cube functions,
that do what their names imply:
lambda_square = lambda { |n| lambda_power.(n, 2) }
lambda_cube = lambda { |n| lambda_power.(n, 3) }The order of the arguments to lambda_power makes these definitions more
awkward. If instead, we had defined it like so:
lambda_power = lambda { |e, n| n ** e }.curryThen we could have done this:
lambda_square = lambda_power.(2)
lambda_cube = lambda_power.(3)That said, we don’t define every function we use. Often we use the functions provided by a library, and if those have inconvenient argument ordering, too bad.
If we had been using an Invocation, we could have done this:
invoke_square = invoke_power.(e: 2)
invoke_cube = invoke_power.(e: 3)True, but the block parameters have names. Since it is a SyntaxError for a
block to have two parameters with the same name, an Invocation can Do The
Right Thing.
Invocations really shines when used with a great REPL like pry.
I’ve uploaded a short screencast here that demonstrates the sort of
information an Invocation provides (using the example scenario above).
Invocations is available under the MIT License. See LICENSE.txt for the
full text.