Pushing Forward

Perl6 will include so called "piping" operators, something I'm looking forward to. The follow example is copied out of the Perl6 Synopsis and shows how you can rewrite this code:

@result = map { floor($^x / 2) },
            grep { /^ \d+ $/ },
               @data;

as:

@data ==> grep { /^\d+$/ }
      ==> map  { floor($^x / 2) }
      ==> @result;

Wait, so why am I looking forward to more gibberish operators?

Well, for one, pipe operators let you write certain chained operations from left to right (ie, postfix notation) which might otherwise have needed to be written right to left. Now obviously, postfix method invocation is pretty standard these days, so anytime you are using methods in those languages (Perl included), you're already going left to right. For example in Ruby:

result = data.grep(/^\d+$/).map{|x| (x.to_f/2).floor}

Of course, this doesn't put the assignment at the end like the Perl6 code does, but that's not a huge deal. You can very clearly tell that grep happens first and map happens second.

Of course, postfix is not just magically always the right way to write things. Take this example Smalltalk code from comp.lang.smalltalk.dolphin:

[:x| x sqrt ] value: ([:x| x + 5 ] value: ([:x| x * x ] value: 2))

See how postfix ordering has given us something that still reads the wrong direction? Admittedly, the blocks are to blame here for reversing the reading direction, but Behavior objects can cause the same problem. Howard Oh suggests adding an arrow method to solve this problem in Smalltalk:

2 --> [:x| x * x ] --> [:x| x + 5 ] --> [:x| x sqrt ] 

Pretty cool. Even cooler, however, is the fact that such a method already exists in Squeak (named "in"). Blaine Buxton shows it off:

((2 in: [:x| x * x ]) in: [:x| x + 5 ]) in: [:x| x sqrt ]

There's some grody parenthesization in there, but that's just part of the price you pay for Smalltalk's method syntax.

Now, Ruby uses ".call()" instead of "value:", but you can get into the same situation with Ruby blocks. Well, actually, you probably won't. Blocks, which are syntactically concise, are only usable when invoking a method. In other situations you must use syntactically unweildy Proc objects. How unweildy? Well, check this out...

Proc.new{|x| Math.sqrt(x) }.call(Proc.new{|x| x + 5}.call(Proc.new{|x| x * x}.call(2)))

or

lambda{|x| Math.sqrt(x) }.call(lambda{|x| x + 5}.call(lambda{|x| x * x}.call(2)))

Yuck, right? Well, the good news is that the "in" syntax actually makes this code something that you'd almost feel okay about writing.

class Object
  def in
    yield self
  end
end

That gets you most of the way there and lets you write:

2.in{|x| x * x }.in{|x| x + 5 }.in{|x| Math.sqrt(x) }

Of course, the big win here is just that we got to use blocks instead of Procs. But you could modify the definition of "in" further like this:

class Object
  def in(*args, &block)
    (block || args[0]).call(self)
  end
end

And write:

2.in(Proc.new{|x| x * x }).in(Proc.new{|x| x + 5 }).in(Proc.new{|x| Math.sqrt(x) })

But you'd be crazy.

posted on: 06/12/2006 | path: /tech