Раздел «Язык Ruby».FunctionalProgrammingOnRuby:

Functional Programming Hacks for Ruby

Нашел интересный пост в блоге Дэвида Фланагана (David Flanagan), который в настоящий момент является активным участником списка рассылки ruby-core.

Привожу его здесь, с частичным переводом на русский.


Я игрался с классами Proc, Method, UnboundMethod и Symbol и получил несколько инструментов для

Я не утверждаю, что эти инструменты промышленно применимы, но они были для меня mind-expanding or character building.

Я не так давно занимаюсь Ruby, поэтому комментарии и замечания от продвинутых рубистов приветствуются.

#
# 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
— Дэвид Фланаган, Июнь 06, 2007