High Order Messages in Python

S

Sam Pointon

This can be suitably applied to Python with the use of Higher Order
Functions, though. It's not quite the Ruby version because Python
allows you to use functions as first-class objects, complicating the
All-You-Can-Do-Is-Pass-A-Message philosophy. This is my 5-minute
implementation:


class HigherOrderList(list):

def do(self, func):
return HigherOrderList(each(self, func))

def where(self, pred):
return HigherOrderList(mass_test(self, pred))

def mass_test(iterable, pred):
for item in iterable:
if pred(item):
yield item

def each(iterable, method):
for item in iterable:
yield method(item)
 
M

Mike Meyer

I'm reading about "high order messages" in Ruby by Nat Pryce, and
thinking if it could be util and if so, if it could be done in Python.
Someone already tried?

Yes, I'm pretty sure it could be done in Python. All it really needs
is the ability to catch references to undefined attributes, which
Python has. You make the HOM of your collection class return an object
with a reference to self, and the __getattr__ method of that classs
then invokes getattr on each object in the referenced collection
instance for the undefined method, manipulating the result as
appropriate for that HOM, and returning a new list.

But this really isn't a very good fit for Python. This is really a
feature for more aggressively OO languages. To make the examples HOMs
described really useful, you want to add them to some base class for
collections. But that base class doesn't exist in Python - Python just
isn't that OO.

You could add various HOMs to lists, tuples, generators and iterators
- but then anyone who wanted to create a new sequence class would have
to add all of the HOMs that their clients might want to use. The
pythonic way would be to add a function that works with all the
various sequence types - which would then automatically work with any
user-defined classes that quacked like a sequence. It's not very OO -
but it is pythonic. In fact, Python already has functions that capture
the functionality of the example HOMs in the links you posted: where
and unless are handled by filter. in_order_of and in_reverse_order_of
are handled by sorted. do is is handled by map. Of course, recent
versions of python provide list comprehensions as preferable to some
of these functions.

That said, HOM's are a *very* powerful mechanism. The examples - and
my discussion of them - cover just one very broad use case. There may
be others where they are a better fit with Python. Having examples of
how to do these kinds of things around is probably worthwhile.

<mike
 
M

Michael

I'm reading about "high order messages" in Ruby by Nat Pryce, and
thinking if it could be util and if so, if it could be done in Python.

Nice sunday afternoon exercise. Yes, you can do this in python. This is
based on a relatively naive translation of the ruby version:

class HigherOrderMessage(object):
def __init__(self,handler):
self.handler = handler

class Do(HigherOrderMessage):
def __HOM__(self, methname, *args):
"implement ruby's method_missing idea"
try:
for e in self.handler:
meth = getattr(e, methname)
meth(*args)
except TypeError: # Handle non-iterator, could be nicer
if self.handler is not None:
meth = getattr(self.handler, methname)
meth(*args)
def __getattribute__(self, methname):
try:
return super(Do,self).__getattribute__(methname)
except AttributeError:
def myHom(*args):
return self.__HOM__(methname, *args)
return myHom

class Where(HigherOrderMessage):
def __HOM__(self, methname, *args):
"implement ruby's method_missing idea"
try:
r = List()
for e in self.handler:
meth = getattr(e, methname)
if meth(*args):
r.append(e)
return r
except TypeError:
r = List()
if self.handler is not None:
meth = getattr(self.handler, methname)
if meth(*args):
r.append(self.handler)
return r
#
def __getattribute__(self, methname):
"Probably belongs in the baseclass"
try:
return super(Where,self).__getattribute__(methname)
except AttributeError:
def myHom(*args):
return self.__HOM__(methname, *args)
return myHom

class enumerable(object):
def do(self): return Do(self)
def where(self): return Where(self)

class List(enumerable,list):
"List using enumerable as a mixin"

class Claimant(enumerable):
def __init__(self, name, age, gender):
self.name = name
self.age = age
self.gender = gender
self.benefits = 0

def retired(self):
return (self.gender == "male" and self.age >= 65) or \
(self.gender == "female" and self.age >= 60)

def receive_benefit(self,amount):
self.benefits = self.benefits + amount

def __str__(self):
return "%s,%s age: %s benefits: %s" % (
self.name,
self.gender,
str(self.age),
str(self.benefits)
)

#
# Create an enumerable list capable of responding to
#

claimants = List([ # Just a list which is enumerable as well
Claimant("tom", 32, "male"),
Claimant("dick", 64, "male"),
Claimant("harry", 128, "male"),
Claimant("Ivanova", 32, "female"),
Claimant("Kochansky", 64, "female"),
Claimant("Sung", 128, "female"),
])

# First the normal python way I prefer this)
for claimant in claimants:
if claimant.retired():
claimant.receive_benefit(50)

# Display the results
for claimant in claimants: print str(claimant)
print

# The more direct translation of :
# claimants.select {|e| e.retired?}.each {|e| e.receive_benefit 50}

[ e.receive_benefit(50) for e in claimants if e.retired() ]

# Display the results
for claimant in claimants: print str(claimant)
print

# The single claimant version of the higher order message approach
# re-uses the last claimant from above. This one unconditionally
# grants benefits.
#
claimant.do().receive_benefit(50)
print claimant
print

# Iterating over a bunch of Claimants with the higher order message
# approach. This conditionally updates the claimaints

claimants.where().retired().do().receive_benefit(50)
# display results
for claimant in claimants: print str(claimant)

I'm not convinced I'd actually *use* this approach(*), but it does show that
you can certainly take this approach in python. It's certainly interesting
though.
(*) Largely because there's a bunch of magic happening as far as the user
(next programmer to edit the file) is concerned. After all you can't
find the definition of retried in "where" or "claimant"; you can't
find the definition of "receive_benefit" in "do", "retired", "where"
or "claimants".

I'm also pretty sure there's other things you'd want to do more than the
above generally speaking if you wanted to handle inheritance/etc nicely.
(That does make me wonder as well if you'd need to do more in ruby as
well)

However, this certainly isn't a case of "ruby can do this, and python
can't", because clearly python CAN do it :)

Regards,


Michael
 
V

vdrab

On a (somewhat) related note,
I've always wondered whether it is possible to emulate ruby blocks
using a python generator '+ alpha'. In my limited understanding of the
ruby block, the generator can inject values into a block, I suppose,
but what is the block itself? can it be a function? a class instance?
what would it look like? I am sure someone must have played around with
this.
any pointers?
cheers,
v.
 
B

bonono

could someone enlighten me what is the advantage of block over named
function ?

One thing that I can see a difference may be lexical scope ?
 
A

Alex Martelli

could someone enlighten me what is the advantage of block over named
function ?

One thing that I can see a difference may be lexical scope ?

"Yes, but" -- according to the latest Ruby book, the "mixed lexical
scope" of blocks is a highly controversial notion in the Ruby community;
so I wouldn't necessarily count it as an _advantage_ of Ruby blocks...


Alex
 
B

bonono

counting that out(regardless whether it is (dis)advantage or not), what
else a block can do but not a named function ?
 
K

Kent Johnson

counting that out(regardless whether it is (dis)advantage or not), what
else a block can do but not a named function ?

My limited understanding is that the advantage is
- simpler syntax
- high level of integration into the standard library (*many* methods that take closure arguments). Blocks are used not just for iteration but for the kinds of things shown in the examples to PEP 343 http://www.python.org/peps/pep-0343.html

For example to open a file and read from it uses two closures, one to wrap a block with the file open/close, one to iterate lines (from the pickaxe book):

File.open("testfile") do |file|
file.each_line { |line| puts line }
end

Kent
 
A

Alex Martelli

Kent Johnson said:
For example to open a file and read from it uses two closures, one to wrap
a block with the file open/close, one to iterate lines (from the pickaxe
book):

File.open("testfile") do |file|
file.each_line { |line| puts line }
end

Good example -- Ruby blocks are used both for iteration, and for
non-iterative wrapping of a single action with "try-finally" semantics.

Python's generators, up to 2.4, don't really support the non-iterative
part of this well, and have other limitations (they can get values "out"
with yield, but can't easily or naturally get results back "in"...). In
the forthcoming 2.5, Python generators will be enriched with enough
functionality for these purposes, and a new statement "with" to clearly
indicate the non-iterative case (while "for" indicates iteration).

So, in Python 2.5, the above snippet may become, in Python:

with opening("testfile") as my_file:
for line in my_file:
print line,

The fact that 2.5 will acquire extra functionality to "round out"
generators &c to the power of Ruby blocks may be taken as a clear
indication that, right now (Ruby 1.8.* vs Python 2.4.*), Ruby's blocks
are somewhat more powerful. As for the differences in style that will
remain when Python 2.5 is born, we'll be back to "personal taste" level
issues, it appears to me.


Alex
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
474,269
Messages
2,571,338
Members
48,025
Latest member
Rigor4

Latest Threads

Top