Hal Fulton wrote:
Ville Vainio wrote:
That is, string can know about sequences, but a sequence, being a more
general data type, should not know about strings.
I’d have to disagree. join, since it produces a string, obviously
knows about strings even though it is not a string method.
I think Ville means that making join a method on a list means that lists
must now know about strings. Unfortunately, removing join as a list
method still leaves lists knowledgeable about string for every object
implements a to_s method.
In fact, knowing nothing but that to_s exists, you can write join like
this …
def join(delimit)
inject { |a, b| a.to_s + delimit + b.to_s }
end
I have also heard (on c.l.py) that one reason the " ".join() form was
chosen was to allow join to be polymorphic on string type. This would
allow join to be implemented differently in subclasses of string (such
as a UnicodeString class perhaps). Since join can be defined in terms
of existing list methods (as we did above), there is no need to make
polymorphic on lists or sequences.
However, making join a string method is neither necessary nor sufficient
for a polymorphic behavior string subclass to be used.
For me, the big reason to make join a method in list is readability.
Allowing methods chains that read linearly left to right is a big plus,
overriding the other considerations in this case. (YMMV)
BTW, neither form is “more OO” than the other.
As far as FP goes, allowing t.split w/o () is extremely non-FP,
because you lose first class functions. And FP without first class
functions is, well, Java.
I don’t know FP either, but I thought method chaining was an important
part of it. Because of the disconnect I mentioned earlier (where join
is not actually acting on its receiver), the previous example doesn’t
show true method chaining.
Neither Ruby nor Python are functional languages, however both exhibit
some features that are found in function languages.
When people think of features of functional languages, they primarily
refer to two features in particular ..
(1) Stateless programming
(2) Higher order functions
Stateless programming means writing code that calculates a result
without changing the state of the program as it executes. This style of
programming has many advantages, but neither Ruby nor Python adheres to
this style, except in small snippets of code.
A functional language treats functions as first class data. This means
that functions can be passed about as data, and that operations can be
performed on these functions that produce new functions. A higher order
function (in my perhaps flawed understanding) is a function that takes
other functions as operands.
Here’s a concrete example … Suppose you had a function f(x,y) that
returns the value x+y. Now write a another function curry(f,n) that
returns a brand-new function object. This new function should take one
argument (x) and return the value x+n (where n was the value passed to
curry).
If your language can write functions like curry, then you have first
class functions and higher order functions. And this can be done in
both Ruby and Python.
[… from an earlier message …]
actually, I tend to think Ruby’s OO
is crippled by not having functions as first class objects.
I find it interesting when people claim that Ruby has no first class
functions, mostly because Ruby doesn’t have functions in the first
place. What looks like functions are really methods on objects.
Methods exist inside an object and like the proverbial quark, are never
seen outside of an object. Unlike functions which are explicitly
called, methods are always invoked by an object in response to receiving
message consisting of a message selector and a list of arguments.
Sometimes we abbreviate and pretend we are calling functions, but it is
still objects, methods and messages[1].
Question: If Ruby methods aren’t function, how can we do functional
programming?
Answer: With a poor man’s function!
At this point it is useful to quote Anton van Straaten where he sums up
his own enlightenment with these koan (see http://tinyurl.com/2m7p9):
The venerable master Qc Na was walking with his student, Anton.
Hoping to prompt the master into a discussion, Anton said "Master, I
have heard that objects are a very good thing - is this true?" Qc Na
looked pityingly at his student and replied, "Foolish pupil -
objects are merely a poor man's closures."
Chastised, Anton took his leave from his master and returned to his
cell, intent on studying closures. He carefully read the entire
"Lambda: The Ultimate..." series of papers and its cousins, and
implemented a small Scheme interpreter with a closure-based object
system. He learned much, and looked forward to informing his master
of his progress.
On his next walk with Qc Na, Anton attempted to impress his master
by saying "Master, I have diligently studied the matter, and now
understand that objects are truly a poor man's closures." Qc Na
responded by hitting Anton with his stick, saying "When will you
learn? Closures are a poor man's object." At that moment, Anton
became enlightened.
If objects are a poor man’s closure, they certainly can be a poor man’s
function as well. In Ruby, we use objects as functions. Any object
that responds to the call message can be used, but by far the most
common functional object is the Ruby Proc/Lambda object.
Here’s the curry operation mentioned above, but done with first class
function objects in Ruby.
f = lambda { |x, y| x + y }
curry = lambda { |f, n| lambda { |x| f.call(n, x) } }
g = curry.call(f, 2)
g.call(3) #=> 5
In summary:
o “def” defines a method, not a function, first class or otherwise.
o What looks like a function call is really a message send to an object.
o Ruby uses objects for its functional programming. Since the function
is an object, calling it involves a normal object-style message using
“call” as the message selector.
I’ll close with this thought. Ruby and Python are perfect examples of a
duality between functions and objects. Where Ruby begins with objects
and builds functions out of them, Python begins with functions and
builds objects out of them. And what is really surprising is that
despite the rather fundamental differences in their semantic foundation,
the languages are more alike than they are different!
···
“Beware of bugs in the above code; I have only proved it correct,
not tried it.” – Donald Knuth (in a memo to Peter van Emde Boas)
[1] I’m not trying to imply that all method invocations involve the
reification of a message. In fact, I have little knowledge of what
goes on under the covers in the C implementation. I’m only saying
that obj.method is semantically equivalent to obj.send(:method).