'How to pre-fill arguments to a function in Ruby?

Let's say I have a function foo:

def foo(A, B, C)
 A + B + C
end

And I call it like this, with only the last parameter changing:

foo("foo", "bar", "123")
foo("foo", "bar", "456")
foo("foo", "bar", "789")

How can I "bake" or "pre-fill" the arguments that do not change? So maybe I would get a new callable foo_baked such that foo_baked("123") is the same as foo("foo", "bar", "123")?

And use it like this:

foo_baked = ...?
foo_baked("123")
foo_baked("456")
foo_baked("789")

Note that I do not want to define a new function using def but want to be able to create foo_baked at runtime on the fly, maybe for an array.



Solution 1:[1]

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')

Solution 2:[2]

You can define a new method foo_baked that wraps foo and passes the non-changeable arguments as hardcoded values:

def foo_baked(c)
  foo("foo", "bar", c)
end

Then it can be called like so:

foo_baked("123")

Alternatively, you can use Ruby's built-in #curry method, as outlined in TonyArra's answer. This approach is more flexible, returning a callable proc until enough arguments are provided.

Solution 3:[3]

You have a few options. The first is a currying approach as outlined in Zoran's answer.

The second is to use default positional arguments. But in order to do this, the optional arguments need to be after the required ones:

def foo_baked(a, b="foo", c="bar")
  [a, b, c]
end

foo_baked(123) # => [123, "foo", "bar"]
foo_baked(123, "asd") 

The third option is to use keyword arguments. The benefit of this approach is that the arguments can be in any order. You can pick and choose which ones you provide values for.

def foo_baked(b: "foo", c: "bar", a:)
  [a, b, c]
end

foo_baked(a: 123) # => [123, "foo", "bar"]
foo_baked(a: 123, b: "asd") # => [123, "asd", "bar"]

A final option would be to combine positional and keyword arguments:

def foo_baked(a, b: "foo", c: "bar")
  [a, b, c]
end

foo_baked(123) # => [123, "foo", "bar"]
foo_baked(123, b: "asd") # => [123, "asd", "bar"]

This has the benefit of still letting you use the positional arguments, but the order of the optional args doesn't matter and they will never interfere with the positional ones

Solution 4:[4]

You could create a lambda wrapping original method:

foo_baked = ->(value) { foo('foo', 'bar', value) }

foo_baked.(123) # => [123, "foo", "bar"]

Note additional . in the execution though. This is ruby special syntax, equivalent to:

foo_baked.call(123)

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2
Solution 3
Solution 4 BroiSatse