

Ruby and Python: pivot points - djacobs
http://allthingsprogress.com/posts/ruby-and-python-pivot-points

======
philwelch
_More importantly (and annoyingly), if I define a top-level method with
def—let’s call those top-level def methods (TLDMs)—Ruby won’t let me pass it
as a block to any other method. (TLDMs actually belong to an object, so
strictly speaking, this makes sense. It’s still annoying.) In Python, we can
pass lambdas and TLDMs like they’re identical.

...

This is a problem because Python treats methods and functions differently. You
can pass functions to other functions. But you can’t pass instance methods._

In Ruby, you can pass "TLDMs" by passing the symbol corresponding to their
name, and calling send() on the symbol to call the method. Likewise, you can
pass the object and the symbol in order to pass a method around.

 _Ruby lacks list comprehensions_

Ruby _does_ have the select method, which is semantically equivalent. List
comprehensions are syntactic sugar for select (and I'll freely admit that Ruby
can have a pretty bitter syntax at times, so maybe the sugar is justified).

~~~
djacobs
I don't think so, select is just a filter. List comprehensions map and select
at the same time.

Regarding TLDMs, how do you propose mapping a TLDM over an array? For example:

    
    
      def transform(x)
        # implementation ...
      end
    
      list = [1, 2, 3]
    
      # doesn't work ...
      list.map &transform
    

_Edit_ : &method(:transform) is way too verbose compared with Python's
solution

~~~
carbon8
You wouldn't use a named Ruby method for something like this. You'd typically
use a Proc or block.

    
    
        [1,2,3].map{ |x| x * 2 }
    

or if you wanted to reuse the block for other things

    
    
        transform = lambda { |x| x * 2 }
        [1,2,3].map(&transform)

~~~
djacobs
Exactly, but there should be an easier syntax that declaring a lambda for each
algorithm I want to map over.

------
xi
_I can stringify a list by saying map(str, numbers), because str() happens to
be a function that I can map with. But I can’t capitalize a list in that way,
because capitalize() is a method._

Yes, you can:

    
    
        >>> map(str.capitalize, ['alpha', 'beta', 'gamma'])
        ['Alpha', 'Beta', 'Gamma']

~~~
kingkilr
Or, if you have a list that might be of mixed types that happen to have
capitalize methods:

    
    
        >>> import operator
        >>> map(operator.methodcaller("capitalize"), your_list)

~~~
vithlani
Hot damn, I had no idea bout that module.....

------
koenigdavidmj
The whole point of having join() as a method on the string is that it is meant
to handle any kind of iterator. You can join a list, a generator, or the
custom iterator of your choice. In any of those cases, it looks the same and
is implemented only once.

If join() was not a string method then it would have to belong in some mixin
that you slap onto your iterators, and that increases code complexity.

Also, you can do things like map(operator.methodcaller("capitalize"),
iterator_of_strings), but that is probably bad style.

And in Ruby you can do your_object.method("foo") to turn your method into a
lambda.

~~~
djacobs
Sounds to me like join would work better as a function ;)

~~~
axiak
Except that it only makes sense in the context of strings, so it makes perfect
sense to make it a string method.

~~~
djacobs
It seems to me like join makes sense in any context where you have multiple
elements that you want joined. With strings, you happen to have a separator.
But you might also want to join several lists into one flattened list. (Edit:
I know there are ways to do this. The point is that join could be unified
around this concept.)

Even if you disagree, the line between methods and functions in Python really
isn't standardized.

~~~
koenigdavidmj
You can join several lists into a flattened list as well:

reduce(operator.add, list_of_lists)

You can not say sum(), sadly, because it forces a restriction that you only
sum numbers.

~~~
dgouldin
It's not pretty, but you can do it:

    
    
      >>> sum([['a', 'b', 'c'], ['d', 'e', 'f']], [])
      ['a', 'b', 'c', 'd', 'e', 'f']
    

This makes use of the optional start argument and list add operator. However,
Python's docs suggest using itertools.chain instead:

<http://docs.python.org/library/functions.html#sum>

    
    
      >>> import itertools
      >>> [l for l in itertools.chain(*[['a', 'b', 'c'], ['d', 'e', 'f']])]
      ['a', 'b', 'c', 'd', 'e', 'f']
    

(Of course you lose the benefit of a generator by using a list comprehension,
but this is just an example.)

~~~
scottbessler
I may be missing something, but why isn't skipping your list comprehension
maintaining the benefit of using a generator?

    
    
       >>> from itertools import chain
       >>> chain(*[['a', 'b', 'c'], ['d', 'e', 'f']])

~~~
dgouldin
It is, but I don't get to show you the result of the chain that way. :)

------
carbon8
_In Ruby, things are more complicated. I need an ampersand to pass a function
and brackets to call it:_

Proc calling is built directly into the language via the _yield_ keyword and
it's by far the most common way to call Procs.

 _More importantly (and annoyingly), if I define a top-level method with
def—let’s call those top-level def methods (TLDMs)—Ruby won’t let me pass it
as a block to any other method._

First, you can indeed pass a method as an argument using _&
method(:method_name)_. Secondly, it's rare to pass around named methods in
Ruby (<http://news.ycombinator.com/item?id=1141245>) because Procs/blocks are
flexible enough to make it almost always unnecessary. See also:
[http://yehudakatz.com/2010/02/21/ruby-is-not-a-callable-
orie...](http://yehudakatz.com/2010/02/21/ruby-is-not-a-callable-oriented-
language/)

------
kqueue
Hooks? You mean method overriding.

Hook as a term is more used in a callback context.

------
rflrob
With regards to the join and capitalize examples, once you consider the
existence of the string module, the current way actually does make more sense.

Capitalizing things only makes sense on a string. Having a builtin function
that could capitalize any input doesn't work, so it's better to explicitly
make it clear that it's a string related function. If, for some reason, you're
allergic to list comprehensions, you could always do map(lambda s:
s.capitalize(), strlist).

Similarly, the string module has a join function, whose definition is: return
sep.join(words). The standard ''.join() is unfortunate and unobvious to the
newcomer, but it's idiomatic, and there probably are unusual cases where
calling a non-method join doesn't work.

~~~
djacobs

      You could always do map(lambda s: s.capitalize(), strlist)
    

That I could, and in fact, I do give an example like that in the accompanying
analysis article [1]. However, it's not simple as Ruby. This is a comparison,
after all.

The two essays I've written are essentially describing what is _natural_ in
each language, not what is strictly possible.

[1] <http://allthingsprogress.com/posts/the-ugliness-of-python>

