Point free (pointless) programming in ruby? (fwd)
John Carter — 2009-01-13 23:15:32
Here's a post I sent to the ruby mailing list about doing pointfree /
concatenative like things in Ruby.
Perhaps some of the Denizens of this list might like to apply their
wondrously creative minds to the exercise.
John Carter Phone : (64)(3) 358 6639
Tait Electronics Fax : (64)(3) 359 4632
PO Box 1645 Christchurch Email :
john.carter@...
New Zealand
---------- Forwarded message ----------
Date: Wed, 14 Jan 2009 12:12:00 +1300 (NZDT)
From: John Carter <
john.carter@...>
To: ruby-talk ML <
ruby-talk@...>
Subject: Point free (pointless) programming in ruby?
I'm very fond of the notion of Concatenative Languages such as Joy,
Factor...
http://en.wikipedia.org/wiki/Joy_(programming_language)
http://www.latrobe.edu.au/philosophy/phimvt/joy/jp-joyjoy.html
http://factorcode.org/
There is a supreme simplicity about them.
So while reading what the current state of them is, I stumbled across
the notion of Point free (sometimes called Pointless) programming.
The idea of sequences of function compositions that elide the
arguments that they will be applied to.
http://www.haskell.org/haskellwiki/Pointfree
One can easily define a sequence of methods in Ruby...
fun_seq = [:a, :b, :c]
as a sequence of symbols.
Which one could apply to an arbitrary argument..
irb
> f = [:to_s, :succ]
=> [:to_s, :succ]
> f.inject(4,:send)
=> "5"
Oooh! Looky! That's sneaky of me!
symbol_sequence.inject(arg,:send)
[:a, :b, :c].inject(arg,:send)
that's equivalent to
arg.a.b.c
Not quite function composition. Cute, but let's try...
irb
> def a(a)
> p ["a",a]
> a+"a"
> end
=> nil
> def b(b)
> p ["b",b]
> b+"b"
> end
=> nil
> def c(c)
> p ["c",c]
> c+"c"
> end
=> nil
> f=[:a,:b,:c]
=> [:a, :b, :c]
> f.inject("foo"){|memo,obj|send(obj,memo)}
["a", "foo"]
["b", "fooa"]
["c", "fooab"]
=> "fooabc"
[:a, :b, :c].inject(arg){|memo,obj|send(obj,memo)}
is equivalent to
c(b(a(arg)))
> c(b(a("bah")))
["a", "bah"]
["b", "baha"]
["c", "bahab"]
=> "bahabc"
Or how about working with lambda's or Proc objects...
> a1 = lambda {|x| x+"a"}
=> #<Proc:0xb7d9f744@(irb):41>
> b1 = lambda {|x| x+"b"}
=> #<Proc:0xb7e0feb8@(irb):42>
> c1 = lambda {|x| x+"c"}
=> #<Proc:0xb7dfa5e0@(irb):43>
> [a1, b1, c1]
=> [#<Proc:0xb7d9f744@(irb):41>, #<Proc:0xb7e0feb8@(irb):42>,
#<Proc:0xb7dfa5e0@(irb):43>]
> [a1, b1, c1].inject("foo"){|memo,proc| proc.call(memo)}
=> "fooabc"
Anyhoo! Clearly in principle one can do Pointfree programming Ruby.
Questions for the Group:
1) Is there a neater way of expressing a sequence of function
compositions in Ruby?
2) Which Ruby Pointfree sequences are actually useful?
John Carter Phone : (64)(3) 358 6639
Tait Electronics Fax : (64)(3) 359 4632
PO Box 1645 Christchurch Email :
john.carter@...
New Zealand
John Nowak — 2009-01-14 01:00:16
On Jan 13, 2009, at 6:15 PM, John Carter wrote:
> 1) Is there a neater way of expressing a sequence of function
> compositions in Ruby?
Evaluation of a concatenative language is just a fold of 'apply' over
your list of functions with the empty list as the initial element. For
example, in Scheme:
(define (ceval fs) (fold apply '() fs))
Your Ruby version is essentially the same thing.
> 2) Which Ruby Pointfree sequences are actually useful?
All you can really do is implement a concatenative language inside
Ruby complete with its own standard library. This is probably not so
useful.
Languages like ML and Haskell work better because they have curried
functions. There even exists a tool for automatically translating
pointful Haskell functions to point-free Haskell functions, although
the results are often less than clear:
\x y z -> y * z - x == flip (flip . ((-) .) . (*))
You may want to look at the following:
http://www.haskell.org/haskellwiki/Pointfree
Keep in mind that all existing concatenative languages make heavy use
of stacks. Related languages like FP and FL make heavy use of lists.
Trying to do pointfree programming in languages that don't do such
things (Ruby, Python, Haskell, Scheme, etc) is going to be quite
painful. For example, here's the Haskell example above in Joy:
* swap -
And in a concatenative FP-style language:
- [hd, * tl]
- John
John Carter — 2009-01-15 22:37:12
On Tue, 13 Jan 2009, John Nowak wrote:
> Languages like ML and Haskell work better because they have curried
> functions.
I wonder if Curried Functions have the same effect as Curried Eggs
have on the digestive system?
Actually Currying works quite well in Ruby...
two_arg = lambda{|a,b| somefunc(a,b)}
b="Some concrete value"
one_arg = lambda{|a| two_arg.call(a,b)}
one_arg.call("Single parameter") is equivalent to
someFunc( "Single Parameter", "Some concrete value")
> Keep in mind that all existing concatenative languages make heavy use
> of stacks. Related languages like FP and FL make heavy use of lists.
> Trying to do pointfree programming in languages that don't do such
> things (Ruby, Python, Haskell, Scheme, etc) is going to be quite
> painful. For example, here's the Haskell example above in Joy:
Well, actually Ruby has Array's that behave (or can be made to behave)
in almost all respects like lists.
The obj.a.b.c.d.e form would be effectively identical to the
concantenative stack to stack functions if obj was an Array and each
method a, b, c, d, e was a non-destructive Array method returning an
Array.
Well, actually Rubies Duck Typing extends that in interesting
ways. See the Ruby Enumerable mixin for an example.
ie. Ruby is a superset of concatenative languages.
ie. If one restricts ones activities to a suitable concatenative
subset, the same interesting properties emerge.
The question then remains... What interesting idioms could one evolve
by playing in that subset.
John Carter Phone : (64)(3) 358 6639
Tait Electronics Fax : (64)(3) 359 4632
PO Box 1645 Christchurch Email :
john.carter@...
New Zealand
John Nowak — 2009-01-16 00:00:21
On Jan 15, 2009, at 5:37 PM, John Carter wrote:
> On Tue, 13 Jan 2009, John Nowak wrote:
>
>> Languages like ML and Haskell work better because they have curried
>> functions.
>
> Actually Currying works quite well in Ruby...
>
> two_arg = lambda{|a,b| somefunc(a,b)}
>
> b="Some concrete value"
> one_arg = lambda{|a| two_arg.call(a,b)}
That is not currying. Unfortunately, this is something of a common
misunderstanding. A curried function is one that emulates taking more
than one argument by using many functions that take a single argument.
For example, here's a function in Haskell that adds three numbers:
foo = \x y z -> x + y + z
And here's *the exact same function* with a different name:
bar = \x -> (\y -> (\z -> x + y + z))
When you write the former, you're really doing the later. Both can be
called identically as they're exactly the same thing:
foo (1, 2, 3) -- 6
bar (1, 2, 3) -- 6
Because 'foo' and 'bar' are curried, we can do this to mean the *exact
same thing* once again; this is called partial application:
((foo 1) 2) 3 -- 6
((bar 1) 2) 3 -- 6
Additionally, Haskell offers a function for currying a function:
curry (\(x, y) -> x + y) == \x y -> x + y
And uncurrying:
uncurry (\x y -> x + y) == \(x, y) -> x + y
Now, you *can* write a curried function in Ruby like so:
baz = lambda{|a| lambda{|b| lambda{|c| a + b + c}}}
And call it as such:
baz.call(1).call(2).call(3) # 6
Unfortunately, it's quite painful.
I'm not sure where the confusion with respect to "curry" started.
Factor is an example of a language that unfortunately misuses the term.
>> Keep in mind that all existing concatenative languages make heavy use
>> of stacks. Related languages like FP and FL make heavy use of lists.
>> Trying to do pointfree programming in languages that don't do such
>> things (Ruby, Python, Haskell, Scheme, etc) is going to be quite
>> painful. For example, here's the Haskell example above in Joy:
>
> Well, actually Ruby has Array's that behave (or can be made to behave)
> in almost all respects like lists.
Yes, you could write all new functions that operate on stacks instead
of using normal parameters. Ruby performance being what it is though,
I can't imagine this being useful for much of anything. Languages like
Factor do analysis to eliminate constant reading from and writing to a
stack which improves performance dramatically. Having to update an
array's contents, make bounds checks, update an array's length, and so
on just to add two numbers doesn't sound like fun.
> ie. Ruby is a superset of concatenative languages.
> ie. If one restricts ones activities to a suitable concatenative
> subset, the same interesting properties emerge.
Yes, if you rewrite every function to work on stacks and use none of
Ruby's features, you will have a very poor concatenative language with
awful syntax. Of course, one can implement Ruby's features in a
concatenative language too. It's therefore a bit silly to claim one is
a superset of the other.
- John
John Carter — 2009-01-16 01:49:16
On Thu, 15 Jan 2009, John Nowak wrote:
> That is not currying. Unfortunately, this is something of a common
> misunderstanding.
You are right, sorry.
> Now, you *can* write a curried function in Ruby like so:
>
> baz = lambda{|a| lambda{|b| lambda{|c| a + b + c}}}
>
> And call it as such:
>
> baz.call(1).call(2).call(3) # 6
>
> Unfortunately, it's quite painful.
Try an alternate syntax then...
irb
irb(main):001:0> baz = lambda{|a| lambda{|b| lambda{|c| a + b + c}}}
=> #<Proc:0xb7d94ab0@(irb):1>
irb(main):002:0> baz[1]
=> #<Proc:0xb7d94ba0@(irb):1>
irb(main):003:0> baz[1][2]
=> #<Proc:0xb7d94c40@(irb):1>
irb(main):004:0> baz[1][2][3]
=> 6
> It's therefore a bit silly to claim one is a superset of the other.
Perhaps a better way of putting it would given the very flexible
multi-paradigm nature of Ruby, one can borrow the "Pointfree"
paradigm as well.
Will it be useful? Well, I suspect it might be.
I haven't seen anything yet that has given me the urge to rip Ruby out
as my main day to day programming workhorse. However there are parts
of the concatenative languages that make feel "ooh that has so much
promise... but not quite something I can convince my boss about".
Ruby doesn't quite comfortably replace the *sh languages yet.
One area that has been a bit hard to express is the *sh '|' pipelines.
I'm playing with the idea of pointfree in ruby as a nice paradigm to
do pipelined oneliners and more.
John Carter Phone : (64)(3) 358 6639
Tait Electronics Fax : (64)(3) 359 4632
PO Box 1645 Christchurch Email :
john.carter@...
New Zealand
John Nowak — 2009-01-16 01:57:02
On Jan 15, 2009, at 8:49 PM, John Carter wrote:
> I haven't seen anything yet that has given me the urge to rip Ruby out
> as my main day to day programming workhorse.
>> x = 0
=> 0
>> x
=> 0
>> lambda{|x| 0}.call(5)
=> 0
>> x
=> 5
That gave me the urge to do something alright.
Yes, I realize it has been fixed. Unfortunately it took 12 years,
which can only mean that those involved have no clue what they're
doing. But now I'm just being rude...
- John