Skip to content

Instantly share code, notes, and snippets.

@Integralist
Last active August 8, 2023 05:10
Show Gist options
  • Save Integralist/9994331 to your computer and use it in GitHub Desktop.
Save Integralist/9994331 to your computer and use it in GitHub Desktop.
Ruby lambdas

Lambda: standard

# Creating a lambda
l = lambda { |name| "Hi #{name}!" }

# Executing the lambda
l.call("foo") # => Hi foo!

Lambda: shorthand

# Creating a lambda using shorthand notation
l = -> name { puts "Hi #{name}!" }

# Excuting the lambda using shorthand notation
l.("foo") # => Hi foo!

# Multiple arguments
l = -> name, age { puts "Hi #{name}! You're #{age} years young" }
l.("foo", 32) # => Hi foo! You're 32 years young

# No arguments
l = -> { puts "foo!" }
l.() # => "foo!"

Closures

Lambda's also enforce a closure and so are able to keep their context across objects, as demonstrated below:

require "json"

class Bar
  attr_reader :l

  def initialize(h = {})
    @l = h[:l] || -> _ { p "no-op"; false }
  end

  def dothing
    result = l.("Mark")
    p "result = #{result}"
  end
end

class Foo
  def initialize
    @h = {
      :l => -> name { p "hello #{name}"; foo_test }
    }
    @bar = Bar.new(@h) # remove @h to test for defensive behaviour
  end

  def start
    @bar.dothing
  end

  private

  def foo_test
    p "I'm internal to Foo class"
    raise ::JSON::ParserError
    true # never reached due to above line triggering an error
  rescue ::JSON::ParserError
    p "caught an error"
    false
  end
end

foo = Foo.new
foo.start

The default output of the above program is:

"hello Mark"
"I'm internal to Foo class"
"caught an error"
"result = false"

Lambda: partial application vs currying

Partial function aplication is calling a function with some number of arguments, in order to get a function back that will take that many less arguments.
Currying is taking a function that takes n arguments, and splitting it into n functions that take one argument.
In order to give you a clearer idea of what each of these two things will do a function, let’s take an example Proc:
proc { |x, y, z| x + y + z }
Partial application of this function would return, if we passed in the first two arguments, the following nested Procs:
proc { |x, y| proc { |z| x + y + z} }
On the other hand, currying this function would return the following nested Procs:
proc { |x| proc { |y| proc { |z| x + y + z} } }
Note that you can only pass in one argument at a time to the result of a curried function, but pass as many as you like at a time when using partial application. This is the core principal that defines these two applications. The Proc#curry method in Ruby allows you to execute both of these applications.

Lambda: curry

Currying: continuously partially apply a handler function until it receives all its expected requirements before invoking. Any remaining arguments will be passed on at invocation.

.curry returns a curried proc. If the optional arity argument is given, it determines the number of arguments. A curried proc receives some arguments. If a sufficient number of arguments are supplied, it passes the supplied arguments to the original proc and returns the result. Otherwise, returns another curried proc that takes the rest of arguments.

# Example 1
l = lambda { |x, y, z| x + y + z }
l.curry[1][2][3] # => 6

# Example 2
a = l.curry[1] # => <Proc:0x007fc759a22920 (lambda)>
b = a[2]       # => <Proc:0x007fc759a68b00 (lambda)> 
b[3]           # => 6

# Better real world example
apply_math = -> fn, a, b { a.send fn, b }
add = apply_math.curry.(:+)
add.(1, 2) # => 3
increment = add.curry.(1)
increment.(1) # => 2
increment.(5) # => 6

Arity

the arity of a function or operation is the number of arguments or operands the function or operation accepts

Arity is only useful when using an actual Proc and not a lambda. It's best to think of a lambda like it's an anonymous function; where as a Proc is more like code being 'included' into another chunk of code.

For example if you pass in a Proc to another function then the reason things like the number of arguments you pass to the Proc, and how the Proc's return values work (i.e. returning from a Proc also returns out of the containing function) is because effectively the Proc's code is injected into that other function. But as the lambda acts like a real anonymous function it will error if called with the wrong number of arguments and when executing a return it'll return out of only that specific block of code and doesn't effect the surrounding function code it was called within.

Now the reason you need to know all this is that being able to use partial application via the arity argument will only work with a Proc. If you use a lambda then by its very nature will throw an error about incorrect number of arguments.

The following is an example of using arity:

p = proc { |x, y, z| x + y + z }
add_to_the_value_three = p.curry(2)
add_to_the_value_three[1][2] # => we're setting up the Proc to have first two args pre-filled (x, y == 1, 2)
# Note: we more likely would've done p.curry(2)[1][2]
add_to_the_value_three[6] # => 9
@RedFred7
Copy link

great stuff! I was totally unaware of the currying functionality and I'm no newbie either. Truly informative piece!

@beezee
Copy link

beezee commented Dec 15, 2015

Sadly, currying appears to break #arity.

e = ->(a, b) { a + b }
e.arity # => 2
e.curry.arity # => -1

Here's a monkey patch that adds a #real_arity method which does not break - https://gist.github.com/beezee/cb60b16a9b04d30123fc

@Ruekompa
Copy link

Thanks. Very nice. I happened to notice a small typo "aplication", the 3rd word in https://gist.github.com/Integralist/9994331#lambda-partial-application-vs-currying.

@kevinjom
Copy link

Thanks you mate, this is impressive.

@sacthedev
Copy link

Thanks...Just what I was looking for.

@timcarl
Copy link

timcarl commented Apr 12, 2018

Well done! Great information!
Thank you.

@thadeu
Copy link

thadeu commented Apr 4, 2019

Great! Thank for this :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment