Currying a proc with keyword arguments in Ruby
You could build your own keyword-flavored curry method that collects keyword arguments until the required parameters are present. Something like:
def kw_curry(method)
-> (**kw_args) {
required = method.parameters.select { |type, _| type == :keyreq }
if required.all? { |_, name| kw_args.has_key?(name) }
method.call(**kw_args)
else
-> (**other_kw_args) { kw_curry(method)[**kw_args, **other_kw_args] }
end
}
end
def foo(a:, b:, c: nil)
{ a: a, b: b, c: c }
end
proc = kw_curry(method(:foo))
proc[a: 1] #=> #<Proc:0x007f9a1c0891f8 (lambda)>
proc[b: 1] #=> #<Proc:0x007f9a1c088f28 (lambda)>
proc[a: 1, b: 2] #=> {:a=>1, :b=>2, :c=>nil}
proc[b: 2][a: 1] #=> {:a=>1, :b=>2, :c=>nil}
proc[a: 1, c: 3][b: 2] #=> {:a=>1, :b=>2, :c=>3}
The example above is limited to keyword arguments only, but you can certainly extend it to support both, keyword arguments and positional arguments.
Currying out of order in Ruby
Currently standard library of Ruby doesn't provide such option.
However you could easily implement a custom method which allows you to change the order of the arguments of Procs and lambdas. For example I'll present an imitation of Haskell's flip
function:
flip f
takes its (first) two arguments in the reverse order off
How would it look like in Ruby?
def flip
lambda do |function|
->(first, second, *tail) { function.call(second, first, *tail) }.curry
end
end
And now we can use this method to change the order of our lambdas.
concat = ->(x, y) { x + y }
concat.call("flip", "flop") # => "flipflop"
flipped_concat = flip.call(concat)
flipped_concat.call("flip", "flop") # => "flopflip"
Ruby: provide an argument while turning proc to a block
Regarding your comment:
Strange, but it swaps arguments during the performance
Actually, the argument order is preserved.
curry
returns a new proc that effectively collects arguments until there are enough arguments to invoke the original method / proc (based on its arity). This is achieved by returning intermediate procs:
def foo(a, b, c)
{ a: a, b: b, c: c }
end
curried_proc = foo.curry #=> #<Proc:0x007fd09b84e018 (lambda)>
curried_proc[1] #=> #<Proc:0x007fd09b83e320 (lambda)>
curried_proc[1][2] #=> #<Proc:0x007fd09b82cfd0 (lambda)>
curried_proc[1][2][3] #=> {:a=>1, :b=>2, :c=>3}
You can pass any number of arguments at once to a curried proc:
curried_proc[1][2][3] #=> {:a=>1, :b=>2, :c=>3}
curried_proc[1, 2][3] #=> {:a=>1, :b=>2, :c=>3}
curried_proc[1][2, 3] #=> {:a=>1, :b=>2, :c=>3}
curried_proc[1, 2, 3] #=> {:a=>1, :b=>2, :c=>3}
Empty arguments are ignored:
curried_proc[1][][2][][3] #=> {:a=>1, :b=>2, :c=>3}
However, you obviously can't alter the argument order.
An alternative to currying is partial application which returns a new proc with lower arity by fixing one or more arguments. Unlike curry
, there's no built-in method for partial application, but you can easily write your own:
my_proc = -> (arg, num) { arg * num }
def fix_first(proc, arg)
-> (*args) { proc[arg, *args] }
end
fixed_proc = fix_first(my_proc, 'foo') #=> #<Proc:0x007fa31c2070d0 (lambda)>
fixed_proc[2] #=> "foofoo"
fixed_proc[3] #=> "foofoofoo"
[2, 3].map(&fixed_proc) #=> ["foofoo", "foofoofoo"]
Or fixing the last argument:
def fix_last(proc, arg)
-> (*args) { proc[*args, arg] }
end
fixed_proc = fix_last(my_proc, 2) #=> #<Proc:0x007fa31c2070d0 (lambda)>
fixed_proc['foo'] #=> "foofoo"
fixed_proc['bar'] #=> "barbar"
['foo', 'bar'].map(&fixed_proc) #=> ["foofoo", "barbar"]
Of course, you are not limited to fixing single arguments. You could for example return a proc that takes an array and converts it to an argument list:
def splat_args(proc)
-> (array) { proc[*array] }
end
splatting_proc = splat_args(my_proc)
[['foo', 1], ['bar', 2], ['baz', 3]].map(&splatting_proc)
#=> ["foo", "barbar", "bazbazbaz"]
Collecting keyword arguments in Ruby
It's because the first parameter i
, is a required parameter (no default value), so, the first value passed to the method (in your first example, this is the hash {a: 7, b: 8}
) is stored into it.
Then, since everything else is optional, the remaining values (if any, in this example, there are none) are filled in, as applicable. IE, the second parameter will go to j
, unless it is a named parameter, then it goes to kargs
(or k
). The third parameter, and any remaining--up until the first keyword argument, go into args
, and then any keyword args go to kargs
def foo(i, j= 9, *args, k: 11, **kargs)
puts "i: #{i}; args: #{args}; kargs: #{kargs}"
end
foo(a: 7, b: 8)
# i: {:a=>7, :b=>8}; args: []; kargs: {}
How to pre-fill arguments to a function in Ruby?
Ruby has function-currying built in with the curry method:
def foo(a, b, c)
a + b + c
end
foo_baked = method(:foo).curry.call('foo', 'bar')
# this returns "foobar123"
foo_baked.call('123')
# ArgumentError (wrong number of arguments (given 4, expected 3))
foo_baked.call('123', '234')
Basically, Method#curry
returns a curried proc: a function, pre-loaded with arguments, that will only execute once enough arguments have been passed to satisfy the method signature.
Note that foo_baked.lambda?
returns true
, so a curried proc is actually a lambda. This is important since it means that you'll get an ArgumentError
if you exceed the maximum number of arguments.
You can allow additional arguments beyond the required three by passing an arity argument to curry
.
def foo(*args)
args.join
end
# does not execute since only 2 arguments have been supplied
foo_baked = method(:foo).curry(3).call('foo', 'bar')
# executes on the third argument
# this returns "foobar123"
foo_baked.call('123')
# this returns "foobar123234"
foo_baked.call('123', '234')
Mixing keyword argument and arguments with default values duplicates the hash?
TL;DR ruby allows passing hash as a keyword argument as well as “expanded inplace hash.” Since change_hash(rand: :om)
must be routed to keyword argument, so should change_hash({rand: :om})
and, hence, change_hash({})
.
Since ruby allows default arguments in any position, the parser takes care of default arguments in the first place. That means, that the default arguments are greedy and the most amount of defaults will take a place.
On the other hand, since ruby lacks pattern-matching feature for function clauses, parsing the given argument to decide whether it should be passed as double-splat or not would lead to huge performance penalties. Since the call with an explicit keyword argument (change_hash(rand: :om)
) should definitely pass :om
to keyword argument, and we are allowed to pass an explicit hash {rand: :om}
as a keyword argument, Ruby has nothing to do but to accept any hash as a keyword argument.
Ruby will split the single hash argument between hash
and rand
:
k = {"a" => 42, rand: 42}
def change_hash(h={}, rand: :om)
h[:foo] = 42
puts h.inspect
end
change_hash(k);
puts k.inspect
#⇒ {"a"=>42, :foo=>42}
#⇒ {"a"=>42, :rand=>42}
That split feature requires the argument being cloned before passing. That is why the original hash is not being modified.
How to get around curry evaluating its arguments?
Simple answer
The syntax would be,
(define (check-io-data data)
((curry equal?) (get-io-data) data))
Discussion
Because check-io-data
is a function of one argument, it must be defined as such. Because equal?
is a function with arity = 2, it must be passed two arguments when curried.
In schematic form, this:
> (define f (lambda (x) (equal? 'curried-argument x)))
> (f 'curried-argument)
#t
> (f 1)
#f
Is equivalent to:
> (define (f x) ((curry equal?) 'curried-argument x))
> (f 'curried-argument)
#t
> (f 1)
#f
Illustrative example
#lang racket
;;; Use a generator to simulate
;;; a non-idempotent procedure
(require racket/generator)
(define get-io-data
(infinite-generator
(yield 1)
(yield 2)
(yield 3)))
(define (check-io-data x)
((curry equal?) (get-io-data) x))
(check-io-data 1) ; #t
(check-io-data 1) ; #f
(check-io-data 1) ; #f
(check-io-data 1) ; #t
Conclusion
A curried function has to define
itself as a function [i.e. (define (name arg...)(body))
] to establish the arity of the lambda
created by curry
.
Ruby automatically expands Hash into keyword arguments without double splat
Since x is optional, hash moves over to kwarg argument.
Unspecified keywords raise error in that case:
def foo(name:)
p name
end
foo # raises "ArgumentError: missing keyword: name" as expected
foo({name: 'Joe', age: 10}) # raises "ArgumentError: unknown keyword: age"
Check out this article
Related Topics
Rubymine 6.0.2, Unable to Debug
How to Run Rails App in a Completely Isolated Instances of Chrome
Empty Strings at the Beginning and End of Split
Ssl_Connect Syscall Returned=5 Errno=0 State=Sslv3 Read Server Hello a (Openssl::Ssl::Sslerror)
How to Call a JavaScript Function from an HTML.Erb
Why Is the Timezone Off in Delayed_Job
Access 'Self' of an Object Through the Parameters
Can't Start Rails Server - Could Not Find a JavaScript Runtime
Ruby/Rails Actionmailer Not Working with Ntlm
How to Print All the Staged File Names Using Ruby Git Pre-Commit Hook
I Trying to Make a Code That Gives the User a Personal Number After They Have Made an User
How to Access a Class Variable from the Outside in Ruby
Why Is Ruby's Loop Command Slower Than While True
How Can Same Program in Ruby Accept Input from User as Well as Command Line Arguments
Why Can't I Change the Value of Self