Hacker News new | past | comments | ask | show | jobs | submit login
Ruby and Python: pivot points (allthingsprogress.com)
43 points by djacobs on Jan 22, 2011 | hide | past | favorite | 31 comments



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).


> 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.

I should also mention that this is false:

    >>> x = 'a'
    >>> def call(f): return f()
    ... 
    >>> call(x.capitalize)
    'A'


I stand corrected. However, I do think that in order to use this like I want ...

map(str.capitalize, ['a', 'b', 'c'])

... you have to understand far too much of Python's implementation (for example, that str is not a function, but a class that kindof acts like a function) to program. I still think that methods + functions = a pain point in Python.


Well... to make it easier you can think of classes as normal functions returning instances. For almost any practical purpose, that's true. Also, str.capitalize is (for any practical purpose again :) ) a one-argument function that takes a string and returns a capitalised string.


In Python, str is a class. And all classes act as function. If there is ever any doubt, you can tell like this:

    >>> type(str)
    <type 'type'>
You can tell that str is a class because it inherits from type type.


also to access TLDMs ruby gives us Object#method, but it's kind of gross.

  class Foo
    def bar
      p 'baz'
    end
  end

  bar = Foo.new.method(:bar).to_proc

  3.times &bar
This can also be used if you have a function such as puts where the object is passed in so you can do things such as...

  [1,2,3].each(&method(:puts))
  #instead of
  [1,2,3].each {|i| puts i }
but that's ALSO pretty gross, and usually considered poor form.


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


    def transform(x)
      x * 2
    end
    
    list = [1, 2, 3]
    
    p list.map &method(:transform)
<3 <3 <3 <3


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)


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


That's probably why explicitly calling transform inside the block, or making transform a method of the objects you're trying to transform, is more idiomatic Ruby.

You're right in that list comprehensions are semantically equivalent to combining map and select, not just select.


    alias :m :method
<3 <3 <3


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']


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)


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


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.


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


See the design FAQ here: http://docs.python.org/faq/design.html#why-is-join-a-string-...

Admittedly, the join syntax is somewhat counter-intuitive and arguably ugly, but it does the job without defining new built-in functions or adding extra syntax. If you really can't stand the syntax, you can always do this:

    # old style way of joining strings
    import string
    string.join([1,2,3], ' ')
or:

    str.join(' ', [1,2,3])
Of course method #1 requires importing an extra module (which can be a bit of a pain) and method #2 requires knowing more about python's implementation of str (as you argued about using str.capitalize), but they work. Generally though, the syntax isn't that bad, but you have alternatives if you want them.


$ipython

In [1]: x = [1,2,3]

In [2]: y = [4,5,6]

In [3]: x + y

Out[3]: [1, 2, 3, 4, 5, 6]

Why use 6 [join()] characters when you can use 1 :)


Polymorphism.


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


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.


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.


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.)


Your second example is easier to just write as:

  >>> from itertools import chain
  >>> list_of_lists = [['a', 'b', 'c'], ['d', 'e', 'f']]
  >>> list(chain(*list_of_lists))
  ['a', 'b', 'c', 'd', 'e', 'f']
That is, list(«foo») is clearer and more idiomatic than [x for x in «foo»].


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']])


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


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...


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.


  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


Hooks? You mean method overriding.

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




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: