I've been noodling around with Proc, Method, UnboundMethod and Symbol, and have come up with the following utilities for:
- function composition
- function memoization
- function currying
- symbol-to-method conversion (different than Symbol.to_proc)
I don't claim that any of this is actually useful. But it might be mind-expanding or character building.
I'm relatively new to Ruby, so comments from more experienced rubyists are more than welcome.
The code follows...
#
# func.rb: experimental functional programming hacks for Ruby
# Copyright (c) 2007 by David Flanagan
# License: http://creativecommons.org/licenses/by/3.0/
#
# This module defines methods and operators for function composition,
# memoization and currying. It automatically includes itself in Proc and
# Method. It is suitable for use with any Proc-like class that
# responds to []. The methods and operators always return lambdas
# regardless of the type of argument.
module Functional
# Return a new lambda that computes self[f[args]]
# Examples, using the * alias for this method
#
# f = lambda {|x| x*x }
# g = lambda {|x| x+1 }
# (f*g)[2] # => 9
# (g*f)[2] # => 5
#
# def polar(x,y)
# [Math.hypot(y,x), Math.atan2(y,x)]
# end
# def cartesian(magnitude, angle)
# [magnitude*Math.cos(angle), magnitude*Math.sin(angle)]
# end
# p,c = method :polar, method :cartesian
# (c*p)[3,4] # => [3,4]
#
def compose(f)
if self.respond_to?(:arity) && self.arity == 1
lambda { |*args| self[f[*args]] }
else
lambda { |*args| self[*f[*args]] }
end
end
#
# Return a new lambda that caches the results of this function and
# only calls the function when new arguments are supplied.
# Example: a memoized recursive factorial function
#
# f = lambda {|x|
# return 1 if x==0
# x*f[x-1];
# }.memoize
#
def memoize
cache = {}
lambda { |*args|
# notice that the hash key is an array of arguments!
unless cache.has_key?(args)
cache[args] = self[*args]
end
cache[args]
}
end
#
# Return a lambda equivalent to this one with one or more initial
# arguments curried in. When only a single argument
# is being specified, the >> alias may be simpler to use.
# Example:
# product = lambda {|x,y| x*y}
# doubler = lambda >> 2
#
def curry_first(*first)
lambda {|*rest| self[*first.concat(rest)]}
end
#
# Return a lambda equivalent to this one with one or more final arguments
# curried in. When only a single argument is being specified,
# the << alias may be simpler
# Example:
# difference = lambda { |x,y| x-y }
# decrement = difference << 1
#
def curry_last(*last)
lambda {|*rest| self[*rest.concat(last)]}
end
# Here are operator alternatives for these methods
alias * compose # h = f*g
alias ~ memoize # cached_f = ~f
alias >> curry_first # g = f >> 2 -- set first arg to 2
alias << curry_last # g = f << 2 -- set last arg to 2
end
# Add these functional programming methods to Proc and Method classes
class Proc; include Functional; end
class Method; include Functional; end
#
# Add [] and []= operators to the Symbol class for accessing and setting
# singleton methods of objects. Read : as "method" and [] as "of".
# So :m[o] reads "method m of o".
#
class Symbol
# Return the method of obj named by this symbol. This may be a singleton
# method of obj (such as a class method) or an instance method defined
# by obj.class or inherited from a superclass.
# Examples:
# creator = :new[Object] # Class method Object.new
# doubler = :*[2] # * method of 2
#
def [](obj)
obj.method(self)
end
# Define a singleton method on object o, using Proc or Method f as its body.
# This symbol is used as the name of the method.
# Examples:
#
# :singleton[o] = lambda { puts "this is a singleton method of o" }
# :class_method[String] = lambda { puts "this is a class method" }
#
# Note that you can't create instance methods this way. See Module.[]=
#
def []=(o,f)
# We can't use self in the block below, since it is evaluated in the
# context of a different object. So we have to assign self to a variable
sym = self
# This is the object we define singleton methods on
eigenclass = (class << o; self end)
# define_method is private, so we have to use instance_eval to execute it
eigenclass.instance_eval { define_method(sym, f) }
end
end
#
# Define [] and []= operators for accessing and setting the instance
# methods of a module. Note that [] returns an UnboundMethod which
# must be bound to an object before it can be invoked.
#
class Module
# Access instance methods with array notation. Returns UnboundMethod
# Example: String[:reverse].bind("foo").call => "oof"
alias [] instance_method
# Define instance methods with array assignment notation
# Example: String[:backwards] = lambda { reverse }
def []=(sym, f)
self.instance_eval { define_method(sym, f) }
end
end
#
# Use the [] operator to bind unbound methods
#
class UnboundMethod
# Allow [] as an alternative to bind. With Module.[] above, we can
# write code like this: puts String[:reverse]["foo"][]
# The first brackets get the method, the second bind it, the third call it.
alias [] bind
end





@__DavidFlanagan
hi David,
Nice. I'm also a ruby beginner. Are you working on a "Ruby in a Nutshell" book ?
BR,
~A