Hacker News new | ask | show | jobs
by pmontra 1954 days ago
You can pass a function as argument using its name in Python and then call it. You must pass it as a symbol in Ruby and then send to it.

  def f(x):
    return x

  def g(fn, x):
    return fn(x)

  g(f, 1)
4 comments

Ruby can definitely pass around several varieties of closure and related constructs, including procs, blocks, lambdas, bindings, continuations, fibers, and both bound and unbound methods.

Whether we should is another matter, and the syntax and idioms certainly lean towards preferring a symbolic late binding, but the language is multi-paradigm, and one may write purely functional Ruby if desired, immutable values and all.

No, passing a symbol and sending is a different mechanism.

The send method is basically the same thing as calling a method by name, as in "obj.foo" == "obj.send(:foo)". If you only pass a symbol into your "caller" method, the symbol goes through the normal lookup: does the receiving object respond to this? If yes, then that implementation (at that exact moment) is called, if not, you get a method_missing.

You're right that that's not how you do first-class functions in ruby. Your example in ruby would be:

  def get_and_call(fn, x)
    fn.call(x)
  end

  def upcasinator_method(str)
    str.upcase
  end

  upcasinator_lambda = lambda {|str| str.upcase}

  get_and_call(upcasinator_lambda, 'foobar')
  => "FOOBAR"

  get_and_call(method(:upcasinator_method), 'foobar')
  => "FOOBAR"
As you can see there are two ways to do that -- either you create a Proc (a lambda if you care about arity), which is the first class function in Ruby, and you call that, or you define a method (but that's a method, an OOP concept, a procedure that implicitly operates on an object), you get a hold of it using the "method" method and then you call it.
The exact same thing in Ruby:

    f = ->(x) { x }

    g = ->(fn, x) { fn[x] }

    g[f,1]
or even¹

   h = g.curry(2)[f]
   h[1]
function composition is available:

   f = -> x { x * 2 }
   g = -> y { y + 4 }
   j = f >> g
   k = j << g

   j[1] #=> 6
   k[1] #=> 14
and, topically, the Y combinator:

    y = -> f {
        -> g { g[g] } [
        -> g { f[-> v { g[g][v] }] }
      ]
    }
hence

    fib = y[-> f {
      -> n { n < 2 ? Array(0..n) : f[n-1].then {  _1 << _1[-1] + _1[-2] } }
    }]

    fib[6] #=> [0, 1, 1, 2, 3, 5, 8]
although the more idiomatic Ruby might be

    fibo = Hash.new { |this, n| this[n] = n < 2 ? n : this[n-1] + this[n-2] }

    (0..6).map(&fibo) #=> [0, 1, 1, 2, 3, 5, 8]
in which the Hash self-converts to a closure via the & operator.

Anyone who says "Ruby doesn't have first-class functions" can fight me.

--------------------

⁽¹⁾ notwithstanding that I recall Matz once remarked the curry method is only included as an elaborate joke.

Thank you. I forgot the Proc#[] method.

I never thought I would ever write these words, but the Python equivalent is more beautiful. Ouch.

> You must pass it as a symbol in Ruby and then send to it.

This is just plain wrong. You can absolutely do this, as other commenters have pointed out.

It is common to see Ruby code passing around a symbol and sending it, but my guess as to why this pattern became common is that it can be serialized into plaintext like YAML, stored in a database, and called later, like for a background job in a web app. But there's no need to do it this way.

Another reason you don't tend to see it this way in Ruby is because Ruby optimizes for the common case: calling methods [1]. Because of this, you can easily create very concise DSLs in Ruby, which would never be quite as clean in Python.

1: https://yehudakatz.com/2010/02/21/ruby-is-not-a-callable-ori...