Thursday, September 07, 2006

» Spicey curry in your ruby +

I've been learning OCaml for the past few days, and for being a member of the evil functional programming paradigm (invented by people who actually liked doing calculus!), it's not too bad. It has some cool features like pattern matching, subtyping (variants) and some other fun stuff. But over all, it's just too much of a pain to use (speaking personally). If I ever had to do something that absolutely needed speed or type safety, like write a program that drives my car around town for me and does my errands, then I would probably use it over C/C++; but I honestly can't see myself using it that often. But as I said, it does have some nice features. More than a couple times as I worked through the tutorial I was heard to say "nice", "nifty" and I think I even said "funtastic" once (I talk to myself, out lound, often -- the meds aren't working, heh).

One of the great things about OCaml (and most of its functionalistical kindred) is currying (or "partial application"). Currying has something to do with Lambda Calculus and Category Theory and other fields of knowledge that are only useful to people who can count to infinity on their fingers using only self-reference and let bindings. The only thing mortals like us need to know is that it is where you take a method that has multiple arguments, partially apply it (i.e., make a new method with one of the arguments consumed), and return a new method that takes n-1 arguments. So, for example, method m:

def m(a, b, c)
print a, b, c
end

Could be curried (doesn't actually work in ruby, just showing the idea) like so:

m_bc = m('value a')

And what that does is, in essence, make a new method named m_bc, that looks like:

def m_bc(b, c)
print 'value a', b, c
end

In other words, m with parameter a consumed. Pretty cool little feature of most functional languages.

Well, python will be getting curry support and some other goodies in 2.5 via the functools module (PEP 309), and you can already find some implementations of curry for python floating around the net, such as:

curry = lambda func, *args, **kw:\
lambda *p, **n:\
func(*args + p, **dict(kw.items() + n.items()))

I just think ruby should have some curry, too! Curry chicken and saffron pilaf for ruby, I say! So to that end, here is a (probably buggy -- I just wrote in in about 10 minutes) implementation of curry for ruby.

class Object
def curry(*args)
if args.size == 1
m = self
a = args.first
elsif args.size == 2
m, a = args
else
raise(ArgumentError,
"`curry': wrong number of " +
"arguments (#{args.size} for 2)",
caller)
end
def _unbnd(m, a)
lambda { |*b| m.bind(a).call(*b) }
end
def _proc(m, a)
lambda { |*b| m.call(a, *b) }
end
if m.is_a?(UnboundMethod)
fun = _unbnd(m, a)
elsif not m.is_a?(Proc)
begin
m = method(m)
fun = _proc(m, a)
rescue NoMethodError, NameError
begin
m = self.instance_method(m)
fun = _unbnd(m, a)
rescue
fun = lambda {}
end
end
else
fun = _proc(m, a)
end
fun
end
end

def add(a, b, c=0)
a + b + c
end

## here's how you use it
add2 = curry(:add, 2)
p add2[4] # => 6
p add2[6] # => 8
add4 = curry(:add, 4)
p add4.call(0) # => 4
add6 = add4.curry(2) # currying other curries
p add6.call(2) # => 8

## you can also curry class/instance methods
## ps. these examples are stupid
arr_rev = Array.curry(:reverse, [1, 2, 3])
p arr_rev.call # => [3, 2, 1]
arr_len = curry(Array.instance_method(:length), [1, 2, 3])
p arr_len.call # => 3
arr_new = Array.curry(:new, [4, 5, 6])
p arr_new.call # => [4, 5, 6]

Anyway, it's not like curry is so amazingly wonderful and let's you do all kinds of things you can't already do, but it's nice for creating little custom Procs out of methods quickly.

In fact, here is one actually useful way to use curry:

debug = curry(:print, '*** DEBUG -> ')
# ... bad stuff
debug["Oh no! You broke something (again)!\n"]
# ... crud, better fix this!
debug["Dude, you totally suck at programming...\n"]

1 Comments:

Anonymous Anonymous said...

here is a ruby gem that provides curry:

https://rubyforge.org/projects/rubymurray/
December 29, 2007 at 3:55 PM

 

Post a Comment

<< Home