Tuesday, April 15, 2008

Functions versus blocks

In response to my last thread, someone answered with the following:

A problem I have with Ruby is that it has been infected with the same "everything must be in a class" nonsense that Java has. OOP is great, but sometimes having free-standing functions are more useful, especially functions that can be be applied to unrelated classes. "len" is a good example of this - because len is a function and not a method you can do things like this:

>>> x = ['abc', [1,2], () ]
>>> map(len, x)
[3, 2, 0]
and
>>> sorted(x, key=len)
[(), [1, 2], 'abc']
If 'len' was a method you would only be able to do this if all the objects that you want to take the length of inherited the 'len' method from a common base class (or mixin, in Ruby). This goes against the whole idea of duck typing.

So in this Python example, if I'm understanding it (and my Python studies) correctly, len is simply a reference to the function "object" that encapsulates logic that determines the length of something. Here's the official definition from the Python docs:

len(s):
Return the length (the number of items) of an object. The argument may be a sequence (string, tuple or list) or a mapping (dictionary).

Ok, so the logic contained in the function object pointed to be the reference "len" gives us the ability to determine the length of a string, tuple, list or dictionary. Great.

Why though is this any better than Ruby? To do the same thing in Ruby, you'd do something like:
irb> x = ['abc', [1,2], {} ]
irb> x.map {|one| one.length rescue nil}
[3, 2, 0]
So in the Python version, len is a builtin function that is passed to map...in Ruby, I simply create a block and pass it to map.

Perhaps the poster didn't realize you can use rescue in this way? It allows you to call length on *any* object and returns nil if that method exists. You could also do:
x.map {|one| one.respond_to? :length ? one.length : nil }
I'm sure one performs better than the other under the sheets, but I haven't tested to verify.

Is Ruby's approach better than Python? In this simplistic example, probably not. However, I think that Ruby's block syntax is more explicit and indicates meaning at the point of usage. Let's say I wanted to do more than just return length...what if I wanted to do some type checking as well?

In Python, I'd have to create a method first to wrap the desired functionality:
def conditional_len(obj):
if isinstance(obj, list):
return len(obj)
else
return None

a = [1, [2, 3], 4]
map(a, conditional_len)
In Ruby, it's (IMO) more obvious what we're trying to accomplish:
a = [1, [2, 3], 4]
a.map do |one|
if one.is_a? Array
return a.length
else
return nil
end
So I'm not sure I follow the argument here. Why exactly is Python's way advantageous compared to Ruby's? Can anyone shed some light?

26 comments:

Woodie said...

I'd say you should probably take a look at the actual usage of interface in Java before slamming the OO approach too hard - especially on an example like: len().

In essence map and sequence become interface definitions. All that's contained in the interface definition are the methods that define the common behavior of a map or sequence. len() might be one such method.

Later when you're implementing various things that are either "maps" or "sequences" - you can declare them as such - and you'll be forced to provide an implementation for the methods the interface specifies. The nice thing about this is that later, when you write a method like:

public void outputLength(Sequence seq) {
System.out.println(seq.len());
}
-or-
public void outputLength(Map map) {
System.out.println(map.len());
}

it works - reliably - always. Because any class which you've created in your application which claims to be a (implement) map or sequence must in turn implement a method named len(). It won't violate D.R.Y. - because typically classes have different internal implementations (otherwise why are they separate classes?).

Note the old adage - Program to an Interface, not an Implementation.

Getting into that whole type checking thing just seems crazy. Yes it's clear - but if you later add some class to your system that is essentially a map or sequence - and it gets handed to the free standing len() function - and the len() function hasn't had an if/then added to it to handle the case of this specific object type what happens? A run time error - or some fall back to an unanticipated length calculation behavior? Even the rescue feature seems a little bunk.

Jake said...

@woodie:

Note that the italicized criticism in the first few paragraphs aren't mine...they came from a commenter on and older thread.

Also note that the type checking was a contrived example...I was just demonstrating that any custom logic used in this way requires a function in Python and a simple block in Ruby.

Elizabeth Wiethoff said...

Jake wrote: "I was just demonstrating that any custom logic used in this way requires a function in Python and a simple block in Ruby."

A Ruby block is something very similar to an anonymous function, a function without a name, a lambda. Python has anonymous functions, but they're not allowed to contain "statements," just an "expression." Hence, if you want Python to use "custom logic," you need to create a function with a name (not an anonymous function) and pass the name around. Why can't an anonymous Python function contain statements? You'll have to ask Guido. This has been hashed over ad nauseum in the Python world, and it makes my eyes glaze over.

Elizabeth Wiethoff said...

Jake, I think all this map(len, x) versus x.map {|one| one.length rescue nil} stuff muddles several issues such that I'm not clear what about Python is bugging you here. Is it Python's combination of __len__() method and built-in len() function that's bugging you? Are you bothered because Python doesn't allow statements in its lambdas? Are you fuzzy on what it means for a function to be a first-class object? There's so much more going on in your "Functions versus blocks" posting than "functions" versus "blocks."

You end your "Functions versus blocks" posting with "So I'm not sure I follow the argument here." I presume you mean the italicized stuff you quote from someone else. I think part of the problem is, the italicized argument makes several mistaken points and your knowledge is too shaky to realize it. :-(

I suggest you read How To Think Like a Computer Scientist: Learning with Python. It's a pretty quick read, and it gives good grounding in the language itself (more thorough than the Python Tutorial). Yes, the first few chapters assume you're a Dummy without programming experience, but stick with it. Then if you're still interested, read Dive into Python, which does more advanced stuff with the language and also dives into several popular library modules. The fact that both of these are a few years old doesn't really matter.

Jake said...

@elizabeth:

Well, the reason I gave any validity to the comment was because you seemed to agree with it in your reply that starts with "This is, of course, easy to almost fix. :-)". Based on the quality of your other answers, I assumed it was me, not you, who was missing something.

To me, Ruby's approach seems not only equivalent, but superior, to that of Python's. However, Dave and you seemed to disagree, so I assumed I was misunderstanding a nuance.

I frequently find the urge to write about my knowledge gaps...it helps me clarify them internally and usually generates a set of really good answers from others. In this case, perhaps my mental stream was a bit too muddied and didn't come across well.

Elizabeth Wiethoff said...

Sorry. I was being facetious with my "easy to almost fix" comment.

I'm glad you're writing about your knowledge gaps, though.

Greg said...

The Right Way(tm) to do this is to have interface-adherence declared separately to both type and interface definition. And of course you have to avoid the bizarre and unhelpful distinction between methods and stand-alone functions, either by not having "methods" in the first place (a pointless sugar if you ask me) or by allowing first-class declaration of methods outside the original type (class) definition.

Then if you want objects of type A to have a len() function as per the HasLen interface, you can define A::len() whenever you like, and further declare that A implements the HasLen interface on the strength of that.

These conditions seem necessary and sufficient to keep all your "ducks" in a row. Java falls short, the dynamically typed languages don't even try to keep them in a row - when your farm-hands are monkeys, your ducks are free-range by definition.

Dave said...

As Elizabeth says, a Ruby block is really an anonymous function definition, so the two pieces of code are equivalent - the difference is that in Python the len function is predefined, while in Ruby you have to define it yourself with a block.

When I first encountered blocks in Smalltalk I thought they were really cool, and I still do. However they are essentially syntactic sugar, and although in some cases the sugar is sweet, in other cases it can get in the way.

For example, how does Ruby deal with situations where you want to pass multiple functions/blocks to a single function? I encountered this situation recently when refactoring some legacy Python code. A class had lots of methods (about 20) that all had a similar structure, but did slightly different things in their bodies. I refactored out the structure into a single template method that took several functions as parameters. Any of the parameters could be None, in which case that step was skipped. The ~20 methods were replaced by methods that called the template method with different parameters, which eliminated a huge amount of code duplication.

I don't know how you would do this in Ruby with blocks, but that could be due to my ignorance of Ruby rather than a deficiency of the language.

Dave said...

Jake said:
"""
what if I wanted to do some type checking as well?

In Python, I'd have to create a method first to wrap the desired functionality:

def conditional_len(obj):
if isinstance(obj, list):
return len(obj)
else
return None

a = [1, [2, 3], 4]
map(a, conditional_len)

In Ruby, it's (IMO) more obvious what we're trying to accomplish:

a = [1, [2, 3], 4]
a.map do |one|
if one.is_a? Array
return a.length
else
return nil
end
"""

Firstly you can do it inline in Python:

map(lamda x: len(x) if isinstance(x, list) else None, a)

although I find it less readable than either the named function or the Ruby block. (BTW, you had the parameters to map the wrong way round).

The advantage of the named function is that it is reusable - I suspect that in Ruby you could easily end up having identical blocks scattered around the code because it is not obvious without close inspection that they are doing the same thing. If you define a named function then it is much more likely to get reused.

PS, does anyone know how to do indented code in blogger comments? It is PITA discussing python when you can't give meaningful examples.

Jake said...

@dave:

Nothing about Ruby prevents you from passing multiple blocks or method references to another method:

irb(main):001:0> a = Proc.new { puts "1" }
irb(main):003:0> b = Proc.new { puts "2" }
irb(main):004:0> c = Proc.new { puts "3" }
irb(main):005:0> def i_take_3 (clsa, clsb, clsc)
irb(main):006:1> clsa.call
irb(main):007:1> clsb.call
irb(main):008:1> clsc.call
irb(main):009:1> end
irb(main):010:0> i_take_3(a, b, c)
1
2
3
irb(main):011:0> def amethod
irb(main):012:1> puts "meth1"
irb(main):013:1> end
irb(main):014:0> def bmethod
irb(main):015:1> puts "meth2"
irb(main):016:1> end
irb(main):017:0> def cmethod
irb(main):018:1> puts "meth3"
irb(main):019:1> end
irb(main):022:0> refa = method :amethod
irb(main):023:0> refb = method :bmethod
irb(main):024:0> refc = method :cmethod
irb(main):025:0> i_take_3(refa, refb, refc)
meth1
meth2
meth3

And yes, I know about lamba expressions in python, and while powerful, they don't come close to Ruby's blocks. I only can hope that Py3K will introduce multi-line lambdas...

I'm really starting to wonder if criticisms of Ruby from the Python community are less substance and more misunderstanding of the language?

Anonymous said...

You should really give up trying to learn Python, it's from the 90's. Python used to be a good replacement for Perl, not any more. The only reason why Python is still struggling is because the guy who created it works for Google.

These are some reasons why you should give up Python:

1. It's an old scripting language.
2. Small community (after 17 years)
3. It's good for math, for not that web development.
4. The Django framework lost track of Rails long time ago.
5. You wont find more than 100 descent Web apps (after 17 years)
6. Plone is the only decent CMS it has, but it's also from the 90's.

Good luck!

Elizabeth Wiethoff said...

Python 3000 will not introduce multiline lambdas. Lambdas just aren't considered "Pythonic" anyway. Lambda was added to the language some years ago apparently to make a Lisper happy. In fact, Guido wanted to get rid of lambda altogether for Python 3000.

The normal way to get things done in Python is to use named functions (or you can use list comprehensions/generator expressions if suitable). And the normal way to get things done in Ruby is to use blocks. Each is approximately as powerful as the other. I think wishing Python had multiline lambdas is kinda like wishing Lisp had semicolons and infix operators. It ain't gonna happen.

I'll tell you this, though. I wish Ruby had first-class functions more than I wish Python had multiline lambdas. Meanwhile, Matz seems to be trying to decide how to let users "call" Ruby Procs/lambdas without having to use the "call" syntax. I.e., he seems to be trying to make Procs smell more like Python functions. And the Symbol#to_proc trick makes defining a quickie, single-expression block look like passing a function name, a la Python: compare (1..100).inject(&:+) from the article with reduce(operator.add, xrange(1,101)), and compare (1..100).map(&:to_s).map(&:size) from a footnote with map(len, map(str, xrange(1,101))). Look, ma, no Python lambdas and the Ruby blocks look suspiciously like functions getting passed!

Dave said...

Jake said "I'm really starting to wonder if criticisms of Ruby from the Python community are less substance and more misunderstanding of the language?"

I do not represent the Python community, or anything other than myself. I also am not trying to criticise Ruby (well not much, anyway) - I like it a lot as a language, but I like Python even more. However if you invite people to give you reasons for using Python rather than Ruby then don't be surprised if what you get looks a lot like criticism of Ruby.

Getting back to the subject of this post - "Functions versus Blocks". In your previous example you showed that:

irb(main):001:0> a = Proc.new { puts "1" }

and

irb(main):011:0> def amethod
irb(main):012:1> puts "meth1"
irb(main):013:1> end
irb(main):022:0> refa = method :amethod

are essentially equivalent - In both cases you are defining a callable object and binding it to a name. I find the extra hoops you have to go through to get at the function object irritating, but I understand the reason.

Barry Andrews said...

"A problem I have with Ruby is that it has been infected with the same "everything must be in a class" nonsense that Java has."

Where did this come from? Completely not true.

Ryan said...

Those specific examples in Ruby would probably be better handled by python list comprehensions:

x.map {|one| one.respond_to? :length ? one.length : nil }

is more or less equivalent to:
[len(x) for x in xs if hasattr(x, "__len__")]

This is read as "len(x) for each x in the iterable xs that has the attribute __len__"

To do it with typechecking:
[len(x) for x in xs if isinstance(x, list)]

You can also use the ternary if operator in list comprehensions now.

Python starts off from a different set of "axioms" than Ruby, but ends up more or less at the same place.

Dave said...

"Where did this come from? Completely not true."
- Barry Andrews said about my comment that Ruby is infected with the "everything must be in a class" meme.

It comes from the Pickaxe book - "Outside a class or module definition, a definition with an unadorned method name is added as a private method to class Object, and hence may be called in any context without an explicit receiver."

So all functions are members of a class, even if you dont specify one.

It is also apparent from looking at the library - most of the things that would be a stand-alone function in Python are class or mixin methods in Ruby - map, length, min, max, etc. This means that Ruby programs use inheritance a lot more, since if you do not inherit from the right base class you cannot use those methods.

krunk- said...

"A problem I have with Ruby is that it has been infected with the same "everything must be in a class" nonsense that Java has."

I'm pretty sure that Python suffers the same ill (if you view it as such). DiveIntoPython 2.4 Everything is an Object

"In case you missed it, I just said that Python functions have attributes, and that those attributes are available at runtime.

A function, like everything else in Python, is an object."

I'm no expert, so if I'm wrong please provide an example that throws an exception here:

for e in my_list: assert isinstance(e,object)

I gave a little method a shot:
def foo():
print('foo')
assert isinstance(foo, object)

Passed with flying colors.

Anonymous said...

Who knows where to download XRumer 5.0 Palladium?
Help, please. All recommend this program to effectively advertise on the Internet, this is the best program!

DeadLy said...

thank you.. sohbet | cinsel sohbet | sohbet | mirc | chat | sohbet

Anonymous said...

lez sohbet odaları
lezsohbet
cinsel sohbet thanks

Anonymous said...

lez sohbet odaları
lez sohbet
cinsel sohbet odaları
türkiye sohbet
çet odaları

chat
yetiskin sohbet
denizli sohbet
chat sohbet
cinsel sohbet

Huub van Eijndhoven said...

I know it 6 years later...
But Python has 'duck typing' at the core / how the built-in functions are defined.
You quoted the manual correctly, but it was a beginners level definition of built-in function len(). The real len() function doesn't care about the type/class of the object, it tries to figure out whether the object supports a interfaces needed to establish length. The most easy thing a class can do to make sure len() can establish sizes of it's objects is define the method __len__ . or you could make your objects iterable by defining __iter__ and __next__.
In below example mySizedClass defines __len__ returning the number of attributes of itself but of course you could make it return anything you deem useful.
class MySizedClass():
def __len__(obj):
return len(obj.__dict__)
it = MySizedClass()
print(len(it))
it.firstAttr = 0
print(len(it))

sence kaya said...



cinsel sohbet
cinsel sohbet odalari
cinsel chat
cinsel chat odalari
pasif sohbet
pasif sohbet odalari
pasif chat
pasif chat odalari
gabile chat odalari
gabile chat
gabile sohbet
sohbet
gabile sohbet odalari
sohbet odalari
chat
chat odalari
mobil chat
mobil sohbet
istanul sohbet
izmir sohbet
ankara sohbet
bursa sohbet

sence kaya said...

Sohbet Chat Sohbet odalari Chat odalari Cinsel Sohbet Gabile Sohbet Pasif Sohbet Sohbet sitesi
Chat sitesi

sohbet said...

Çok güzel bir blog sayfası olmuş

sohbet said...

sohbet
mobil sohbet