Uniform Function Call Syntax (UFCS)

Steven D'Aprano steve+comp.lang.python at pearwood.info
Sun Jun 8 23:20:39 EDT 2014


On Mon, 09 Jun 2014 03:10:03 +1000, Chris Angelico wrote:
[...]
> Actually, this is something that I've run into sometimes. I can't think
> of any Python examples, partly because Python tends to avoid unnecessary
> method chaining, but the notion of "data flow" is a very clean one -
> look at shell piping, for instance. Only slightly contrived example:
> 
> cat foo*.txt | gzip | ssh other_server 'gunzip | foo_analyze'
> 
> The data flows from left to right, even though part of the data flow is
> on a different computer.
> 
> A programming example might come from Pike's image library
[...]

> Stdio.write_file("foo.png",Image.PNG.encode(Image.JPEG.decode(
> Stdio.read_file("foo.jpg")).autocrop().rotate(0.5).grey()));
> 
> With UFCS, that could become perfect data flow:
> 
> read_file("foo.jpg").JPEG_decode().autocrop().rotate(0.5).grey()
> .PNG_encode().write_file("foo.png");

As far as I am concerned, the biggest problem with chained method calls 
is that it encourages long one-liners. But I think chained calls are 
quite natural to read, and rather similar to the postfix notation used by 
Forth:

"foo.jpg" read_file JPEG_decode autocrop 0.5 rotate grey PNG_encode 
"foo.png" write_file


Although Forth has a (justified) reputation for being hard to read, 
postfix notation is not the cause. The above can be understood easily as 
a chain of function calls: read the file, then decode, then autocrop, 
then rotate, they grey, then encode, then write the file. You read and 
write the calls in the same first-to-last order as you would perform them.

The equivalent prefix notation used by function calls is unnaturally 
backwards and painful to read:

write_file(PNG_encode(grey(rotate(autocrop(JPEG_decode(
  read_file("foo.jpg"))), 0.5))), "foo.png");


> I had to solve the syntactic ambiguity here by importing all the
> appropriate names

I'm not sure how this is *syntactic* ambiguity.

As I see it, the only syntactic ambiguity occurs when you have functions 
of two arguments. Using shell notation:

plus(1, 2) | divide(2)

Assuming divide() takes two arguments, does that give 3/2 or 2/3? I would 
expect that the argument being piped in is assigned to the first 
argument. But I'm not sure how this sort of design ambiguity is fixed by 
importing names into the current namespace.

(Note that Forth is brilliant here, as it exposes the argument stack and 
gives you a rich set of stack manipulation commands.)

While we're talking about chaining method and function calls, I'll take 
the opportunity to link to this, in case anyone feels like adapting it to 
UFCS:

http://code.activestate.com/recipes/578770



-- 
Steven D'Aprano
http://import-that.dreamwidth.org/



More information about the Python-list mailing list