hoodwink.d enhanced
RSS
2.0
XHTML
1.0

RedHanded

Arity Makes Good Curry #

by why in inspect

kig just dropped this snip in the comments of the pipes discussion, which had morphed into a discussion about functional programming.

 class Fun < Proc
   def call(*args)
     if args.size < arity.abs and arity != -1
       Fun.new{|*a| call(*(args+a)) }
     else
       super
     end
   end
   alias_method :[], :call
 end

 def fun &block
   Fun.new &block
 end

 f = fun{|a, b, c| a * b / c }

 f[10, 20, 30] == f[10][20][30]
 #=> true

You can sort of curry methods with this, like so:

 class Turtle
   def draw *args
     fun { |x, y| ... }.call(*args)
   end
 end

But you will need to use the bracket syntax on the function returned by the method.

said on 23 Nov 2005 at 15:09

f = fun {|a, b, *c| [a, b, *c] }
f[1,2] #=> #&lt;Fun:0x02b4ec58> :(
For consistency, and the ability to call *args functions with empty args:

#...
  def call(*args)
 if args.size < arity.abs and args.size != -arity - 1
#...

I predict that the thread below will contain an improved recipe for method currying, so stay tuned!

said on 23 Nov 2005 at 15:34

Oho, good point with -arity. Another thing would be handling block arguments, any idea with that? E.g.

f = fun{|x, &b| x.map(&b) }
double = f.call{|i| i*2 }
double[[1,2,3]]
#=> [2,4,6]
said on 23 Nov 2005 at 16:15

Exactly what you posted will work in Ruby 1.9, but procs can’t take blocks in 1.8.

said on 23 Nov 2005 at 16:40

Pardon, but where is arity defined?

said on 23 Nov 2005 at 16:53

arity is a method of Proc and Method and UnboundMethod.

said on 23 Nov 2005 at 19:46

Here’s arity in Programming Ruby’s online reference if you want to be specific about it.

said on 23 Nov 2005 at 21:26
Continuing with funny thought code experiments, how about mixing Enumerables and Procs?

class Proc
include Enumerable
  attr_accessor :domain
  def each
    domain.each{|*e| yield call(*e) }
  end
  alias_method :range, :entries
end

f = lambda{|x| x * 2 }
f.domain = (1..5)
f.range
#=> [2, 4, 6, 8, 10]

g = lambda{|y| y - 1 }
g.domain = f
g.range
#=> [1, 3, 5, 7, 9]
said on 23 Nov 2005 at 22:24
Of course we could just do

class Proc
  def >> g
    lambda{|*args| g[self[*args]]}
  end
  def over enum
    enum.map &self
  end
end

f = lambda{|x| x * 2 }
g = lambda{|y| y - 1 }
h = f >> g
h.over(1..5)
#=> [1, 3, 5, 7, 9]
But it just doesn’t have the same feeling, somehow.
said on 24 Nov 2005 at 00:38

On Suby we discussed a bit we picked up from Nemerle:

def foo( x, y )
  x + y
end
bar = foo( _, 10 )
bar( 6 )  #=> 16

Apply it too lambdas just as well. Would be a cool feature.

said on 24 Nov 2005 at 12:12

Okay, so we’ve got:

  • monads
  • lazy evaluation
  • automatic currying
  • function composition (via kig’s >> operator)

Any other “scary” functional programming things left to do in Ruby? Because I feel like we are running out.

I mean, I suppose we could always do the old “Ruby Objects are really just functions” routine.


class Object
  alias call send
  alias [] call
  def arity ; -1 ; end
  def to_proc ; method( :send ).to_proc ; end
end

Y’know, because they are. They’re closures over their instance variables. Their first parameter is a method name.

p [ :inspect, :class, [ :'+', 2 ] ].map( &1 )
 => ["1", Fixnum, 3]
said on 24 Nov 2005 at 12:19

(Once we’re done here I think I’m going to make a raskell.rb with all this stuff in. Just for kicks.)

said on 24 Nov 2005 at 12:54

Incidentally, once you see that objects are functions, it’s apparent that Object#method simply curries in the first argument.

said on 24 Nov 2005 at 17:20

Any other “scary” functional programming things left to do in Ruby? Because I feel like we are running out.

Hindley-Milner type inference? I kid, I kid.

The Objects as functions -trick made me giggle with glee, good job.

I found the following amusing:


f = fun{|x| x * 2 }
f.domain = (1..5)

g = fun{|x| x - 2 }
g.domain = (3..7)

h = fun{|x,y| x * y }
h.domain = f

h.zip(g).map{|f,x| f[x] }
#=> [2, 8, 18, 32, 50]
said on 26 Nov 2005 at 06:14

Haskell-style datatype.rb still seems to have room for improvement(cvs.m17n.org/viewcvs/ruby/).

said on 26 Nov 2005 at 09:55

Here is a functional-style one-liner to create n-dim arrays:

md = lambda {|*ds| Array::new(ds.shift || 0).map {md[ds] unless ds.empty? }}; nar = md.call(8, 3, 5); puts nar.inspect

said on 26 Nov 2005 at 10:00

correction: ... md[*ds] ...

said on 29 Nov 2005 at 05:56

(Once we’re done here I think I’m going to make a raskell.rb with all this stuff in. Just for kicks.)

Yes!! Please.

Comments are closed for this entry.