rubyselectmethodssyntaxruby-block

Ruby method call using round brackets throwing syntax error


I'm having some trouble understanding why I can't call select with round brackets.

a = [1,2,3]
a.select {|v| v.is_a?(Integer)}  # works
a.select({|v| v.is_a?(Integer)}) # syntax error, unexpected '|', expecting '}

As far as I can tell select is a regular method.

Array.method_defined? :select # true
Array.method_defined? :is_a?  # true

I though round brackets are optional for methods in ruby.
In the case below round brackets make no difference.

a.reverse() == a.reverse #true

I'm using ruby 2.2.1.
Any ideas?


Solution

  • You cannot pass a block with such a synatx, you would have to do something like this:

    a.select(&lambda { |v| v.is_a?(Integer) })
    

    but normally you would just do

    a.select { |v| v.is_a?(Integer) }
    

    which is the same as

    a.select() { |v| v.is_a?(Integer) }
    

    i.e the block is outside the method parameters.

    You could also use the 'stabby' lambda syntax:

    is_a_integer = ->(v) { v.is_a?(Integer) }
    a.select(&is_a_integer)
    

    So if you want to pass a block as an argument you need to prefix with &, but usually you would have the block outside the parameters for atheistic reasons.

    Also notice the difference between these to method signatures and the way they are called:

    def call(&block)
      yield
    end
    
    call { 1 } # => 1
    
    call(lambda { 1 }) # => ArgumentError
    
    call(&lambda { 1 }) # => 1
    

    and

    def call(block)
      block.call
    end
    
    call { 1 } # => ArgumentError
    
    call(lambda { 1 }) # => 1
    
    call(&lambda { 1 }) # => ArgumentError
    

    This is because lambda (and Procs) are objects hence we can do #call to evaluate them, but blocks are not and can be evaluated using the yield keyword. There is more information in this blog post.

    lambda { 1 }.class # => Proc