Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is Ruby's code block same as C#'s lambda expression?

Tags:

c#

ruby

Are these two essentially the same thing? They look very similar to me.

Did lambda expression borrow its idea from Ruby?

like image 509
Shuo Avatar asked Dec 02 '10 04:12

Shuo


People also ask

What is a Ruby code block?

Ruby code blocks are called closures in other programming languages. It consist of a group of codes which is always enclosed with braces or written between do.. end. The braces syntax always have the higher precedence over the do.. end syntax.

Are Codeblocks objects in Ruby?

A block is the same thing as a method, but it does not belong to an object. Blocks are called closures in other programming languages.

Are Ruby code blocks first class?

Ruby doesn't have first-class functions, but it does have closures in the form of blocks, procs and lambdas. Blocks are used for passing blocks of code to methods, and procs and lambda's allow storing blocks of code in variables.


1 Answers

Ruby actually has 4 constructs that are all extremely similar

The Block

The idea behind blocks is sort of a way to implement really light weight strategy patterns. A block will define a coroutine on the function, which the function can delegate control to with the yield keyword. We use blocks for just about everything in ruby, including pretty much all the looping constructs or anywhere you would use using in c#. Anything outside the block is in scope for the block, however the inverse is not true, with the exception that return inside the block will return the outer scope. They look like this

def foo   yield 'called foo' end  #usage foo {|msg| puts msg} #idiomatic for one liners  foo do |msg| #idiomatic for multiline blocks   puts msg end 

Proc

A proc is basically taking a block and passing it around as a parameter. One extremely interesting use of this is that you can pass a proc in as a replacement for a block in another method. Ruby has a special character for proc coercion which is &, and a special rule that if the last param in a method signature starts with an &, it will be a proc representation of the block for the method call. Finally, there is a builtin method called block_given?, which will return true if the current method has a block defined. It looks like this

def foo(&block)   return block end  b = foo {puts 'hi'} b.call # hi 

To go a little deeper with this, there is a really neat trick that rails added to Symbol (and got merged into core ruby in 1.9). Basically, that & coercion does its magic by calling to_proc on whatever it is next to. So the rails guys added a Symbol#to_proc that would call itself on whatever is passed in. That lets you write some really terse code for any aggregation style function that is just calling a method on every object in a list

class Foo   def bar     'this is from bar'   end end  list = [Foo.new, Foo.new, Foo.new]  list.map {|foo| foo.bar} # returns ['this is from bar', 'this is from bar', 'this is from bar'] list.map &:bar # returns _exactly_ the same thing 

More advanced stuff, but imo that really illustrates the sort of magic you can do with procs

Lambdas

The purpose of a lambda is pretty much the same in ruby as it is in c#, a way to create an inline function to either pass around, or use internally. Like blocks and procs, lambdas are closures, but unlike the first two it enforces arity, and return from a lambda exits the lambda, not the containing scope. You create one by passing a block to the lambda method, or to -> in ruby 1.9

l = lambda {|msg| puts msg} #ruby 1.8 l = -> {|msg| puts msg} #ruby 1.9  l.call('foo') # => foo 

Methods

Only serious ruby geeks really understand this one :) A method is a way to turn an existing function into something you can put in a variable. You get a method by calling the method function, and passing in a symbol as the method name. You can re bind a method, or you can coerce it into a proc if you want to show off. A way to re-write the previous method would be

l = lambda &method(:puts) l.call('foo') 

What is happening here is that you are creating a method for puts, coercing it into a proc, passing that in as a replacement for a block for the lambda method, which in turn returns you the lambda


Feel free to ask about anything that isn't clear (writing this really late on a weeknight without an irb, hopefully it isn't pure gibberish)

EDIT: To address questions in the comments

list.map &:bar Can I use this syntax with a code block that takes more than one argument? Say I have hash = { 0 => "hello", 1 => "world" }, and I want to select the elements that has 0 as the key. Maybe not a good example. – Bryan Shen

Gonna go kind of deep here, but to really understand how it works you need to understand how ruby method calls work.

Basically, ruby doesn't have a concept of invoking a method, what happens is that objects pass messages to each other. The obj.method arg syntax you use is really just sugar around the more explicit form, which is obj.send :method, arg, and is functionally equivalent to the first syntax. This is a fundamental concept in the language, and is why things like method_missing and respond_to? make sense, in the first case you are just handling an unrecognized message, the second you are checking to see if it is listening for that message.

The other thing to know is the rather esoteric "splat" operator, *. Depending on where its used, it actually does very different things.

def foo(bar, *baz) 

In a method call, if it is the last parameter, splat will make that parameter glob up all additional parameters passed in to the function (sort of like params in C#)

obj.foo(bar, *[biz, baz]) 

When in a method call (or anything else that takes argument lists), it will turn an array into a bare argument list. The snippet below is equivilent to the snippet above.

obj.foo(bar, biz, baz) 

Now, with send and * in mind, Symbol#to_proc is basically implemented like this

class Symbol   def to_proc     Proc.new { |obj, *args| obj.send(self, *args) }   end end 

So, &:sym is going to make a new proc, that calls .send :sym on the first argument passed to it. If any additional args are passed, they are globbed up into an array called args, and then splatted into the send method call.

I notice that & is used in three places: def foo(&block), list.map &:bar, and l = lambda &method(:puts). Do they share the same meaning? – Bryan Shen

Yes, they do. An & will call to_proc on what ever it is beside. In the case of the method definition it has a special meaning when on the last parameter, where you are pulling in the co-routine defined as a block, and turning that into a proc. Method definitions are actually one of the most complex parts of the language, there are a huge amount of tricks and special meanings that can be in the parameters, and the placement of the parameters.

b = {0 => "df", 1 => "kl"} p b.select {|key, value| key.zero? } I tried to transform this to p b.select &:zero?, but it failed. I guess that's because the number of parameters for the code block is two, but &:zero? can only take one param. Is there any way I can do that? – Bryan Shen

This should be addressed earlier, unfortunately you can't do it with this trick.

"A method is a way to turn an existing function into something you can put in a variable." why is l = method(:puts) not sufficient? What does lambda & mean in this context? – Bryan Shen

That example was exceptionally contrived, I just wanted to show equivalent code to the example before it, where I was passing a proc to the lambda method. I will take some time later and re-write that bit, but you are correct, method(:puts) is totally sufficient. What I was trying to show is that you can use &method(:puts) anywhere that would take a block. A better example would be this

['hello', 'world'].each &method(:puts) # => hello\nworld 

l = -> {|msg| puts msg} #ruby 1.9: this doesn't work for me. After I checked Jörg's answer, I think it should be l = -> (msg) {puts msg}. Or maybe i'm using an incorrect version of Ruby? Mine is ruby 1.9.1p738 – Bryan Shen

Like I said in the post, I didn't have an irb available when I was writing the answer, and you are right, I goofed that (spend the vast majority of my time in 1.8.7, so I am not used to the new syntax yet)

There is no space between the stabby bit and the parens. Try l = ->(msg) {puts msg}. There was actually a lot of resistance to this syntax, since it is so different from everything else in the language.

like image 143
Matt Briggs Avatar answered Sep 21 '22 19:09

Matt Briggs