Some useful closures, in Ruby

Posted by Eric Kidd Thu, 01 Feb 2007 18:36:00 GMT

Reginald Braithwaite has just posted a short introduction to closures in Ruby. Closures allow you to pass functions around your program, and build new functions from old ones.

Programming languages that support closures include Perl, Ruby, Python (sorta), Lisp, Haskell, Dylan, Javascript and many others.

The Dylan programming language included four very useful functions built using closures: complement, conjoin, disjoin and compose. The names are a bit obscure, but they can each be written in a few lines of Ruby.

Let’s start with complement:

# Builds a function that returns true
# when 'f' returns false, and vice versa.
def complement f
  lambda {|*args| not f.call(*args) }
end

We can use this to build the “opposite” of a function:

is_even = lambda {|n| n % 2 == 0 }
is_odd  = complement(is_even)

is_odd.call(1) # true
is_odd.call(2) # false

compose is another useful function:

# Builds a function which calls 'f' with
# the return value of 'g'.
def compose f, g
  lambda {|*args| f.call(g.call(*args)) }
end

We can use this to pass the output of one function to the input of another:

mult2 = lambda {|n| n*2 }
add1  = lambda {|n| n+1 }
mult2_add1 = compose(add1, mult2)

mult2_add1.call(3) # 7

The conjoin function is a bit more complicated, but still very useful:

# Builds a function which returns true
# whenever _every_ function in 'predicates'
# returns true.
def conjoin *predicates
  base = lambda {|*args| true }
  predicates.inject(base) do |built, pred|
    lambda do |*args|
      built.call(*args) && pred.call(*args)
    end
  end
end

We can use it to construct the logical “and” of a list of functions:

is_number = lambda {|n| n.kind_of?(Numeric) }
is_even_number = conjoin(is_number, is_even)

is_even_number.call("a") # false
is_even_number.call(1)   # false
is_even_number.call(2)   # true

The opposite of conjoin is disjoin:

# Builds a function which returns true
# whenever _any_ function in 'predicates'
# returns true.
def disjoin *predicates
  base = lambda {|*args| false }
  predicates.inject(base) do |built, pred|
    lambda do |*args|
      built.call(*args) || pred.call(*args)
    end
  end
end

This allows us to construct the logical “or” of a list of functions:

is_string  = lambda {|n| n.kind_of?(String) }
is_string_or_number =
  disjoin(is_string, is_number)

is_string_or_number.call("a") # true
is_string_or_number.call(1)   # true
is_string_or_number.call(:a)  # false

These were four of the first closure-related functions I ever used, and they’re still favorites today.

Feel free to post versions in other languages below!

Tags ,

Comments

  1. Michael said about 2 hours later:

    These are also pretty easy to achieve in Python, as well.

    
    def complement(fn):
        def not_fn(*args, **kwargs):
            return not fn(*args, **kwargs)
        return not_fn
    
    # Composition, assuming f is unary
    def compose(f, g):
        def f_ring_g(*args, **kwargs):
            return f(g(*args, **kwargs))
        return f_ring_g
    
    def conjoin(*preds):
        def conj(*args, **kwargs):
            for p in preds:
                q = p(*args, **kwargs)
                if not q: return q
            return True
        return conj
    
    def disjoin(*preds):
        def disj(*args, **kwargs):
            for p in preds:
                q = p(*args, **kwargs)
                if q: return q
            return False
        return disj
    
    

    The lack of Ruby’s nice, polite anonymous blocks in Python can be really annoying at times, but it’s not that difficult to work around their absence for cases like these. Note that you can shorten up conjoin and disjoin a bit if you are willing to throw out the return values that generated the exit case and just throw back True or False.

  2. Eric Kidd said about 3 hours later:

    Of course, Haskell makes these all very terse:

    complement = (not .)
    compose = (.)
    conjoin = foldr (\f g x -> f x && g x)
                    (const True)  
    disjoin = foldr (\f g x -> f x || g x)
                    (const False)

    In two cases, the Haskell implementation is shorter than the Dylan name!

    Here’s some sample code:

    complement even 1         -- True
    compose (+1) (*2) 3       -- 7
    conjoin [(>=1),(<=10)] 1  -- True
    conjoin [(>=1),(<=10)] 11 -- False
    disjoin [(<1),(>10)] 11   -- True

    Do you know of any languages which can make these examples even shorter?

  3. Devin Ben-Hur said about 7 hours later:

    These handy tools are independent of the idea of closures. The functional composition works for any higher-order function regardless of whether the function definitions close on their environment.

    Also, seems like your composition primitives would lose any block attachments when called. Might what to use *args,&blk every time you accept and propagate the arguments. For compose you’ll need
    
    def compose f, g
      lambda {|*args,&blk| f.call(g.call(*args,&blk),&blk) }
    end
    
    
  4. nmessenger said about 11 hours later:

    @Eric Kidd:

    Haskell’s ‘conjoin’ and ‘disjoin’ can be written even more concisely:

    import Control.Monad(liftM2)
    conjoin = foldr (liftM2 (&&)) (const True)
    disjoin = foldr (liftM2 (||)) (const False)
  5. Eric Kidd said about 14 hours later:

    nmessenger: Interesting! That appears to rely on “Monad ((->) r)”. Here’s the source:

    instance Functor ((->) r) where
      fmap = (.)
    
    instance Monad ((->) r) where
      return = const
      f >>= k = \ r -> k (f r) r

    Frankly, I’m impressed. That’s a monad with some serious potential for abuse.

  6. nablaone said about 14 hours later:

    http://www.lisp.org/HyperSpec/Body/fun_complement.html http://www.lisp.org/HyperSpec/Body/fun_everycm_s_erycm_notany.html

    :-)

  7. Eric Kidd said about 14 hours later:

    nablaone: Yeah, those are also very useful. In Haskell, you could use:

    any even [1]   # False
    any even [1,2] # True
    all even [1,2] # False
    all even [2,4] # True

    And in Ruby:

    [2,4].all? {|n| n%2==0 }
    [2,4].any? {|n| n%2==0 }

    Of course, it’s possible to write both of these higher-order functions without using closures.

  8. Peter Burns said about 22 hours later:

    For conjoin and disjoin in ruby I prefer:

    predicates.all? {|f| f.call(2)}
    predicates.any? {|f| f.call(2)}
  9. Eric Kidd said about 22 hours later:

    Peter Burns: Thanks! That’s a lot nicer.

  10. nmessenger said 1 day later:

    Abuse, elegance, what’s the difference? :P

  11. nmessenger said 1 day later:

    Though it occurs to me that they could be even more transparent.

    conjoin fs x = and (map ($ x) fs)
    disjoin fs x = or  (map ($ x) fs)

    Some transformations and we have:

    conjoin = flip (all . flip id)
    disjoin = flip (any . flip id)

    I better stop now, golfing is addictive. :)

  12. Eric Kidd said 1 day later:

    nmessenger: The map-based versions are nice (if you know what $ does), but the ones with flip are just wrong.

    Update: nmessenger just sent me the shortest and most elegant version yet:

    conjoin fs x = all ($ x) fs
    disjoin fs x = any ($ x) fs
  13. Ola Bini said 1 day later:

    Very nice writeup. Regarding complement for Ruby, I would prefer this, though:

    def complement &b lambda {|args| not b.call(args)} end

    Then you can skip the first reification:

    is_odd = complement {|n| n % 2 == 0}

  14. Darc Fitton said 7 days later:

    Of course, there’s also APL :

    is_odd{assign}2{jot}|

    is_even{assign}~{jot}is_odd

    Where {jot} is the ‘compose’ operator, used in the first example to curry the ‘modulus’ primitive, in the second to compose the ‘not’ primitive and the function.

    Here we use a series of ‘compose’ to obtain the function :

    mult2_plus1{assign}(1{jot}+){jot}(2{jot}{multiply})

    Conjoin and disjoin are easily translated with {and} and {or} primitives and the {reduce} operator.

    {and}/1 0 1

    => 0

    {or}/1 0 1

    => 1

    With the added bonus that all primitives map their arguments :

    2 | 1 2 3 NB. 2 modulus 1 2 3

    => 1 0 1

    We define the operator conjoin :

    r{assign}(f1 conjoin f2) args

    r{assign}^/(f1 args)(f2 args)

    Then use it :

    is_even conjoin is_number 1 2 3

    => 0 1 0

  15. sean said 20 days later:

    It still surprises me how “Haskell Golf” is still considered a mark of high intelligence or nerd-prowess, while golf in any other language is just a game…

  16. Eric said 20 days later:

    Well, that’s the fun thing about Haskell golf1—it quickly degenerates into higher mathematics. “You did what? How is that a monad?” And suddenly you’re hip deep in category theory.

    Provided the golf is educational, I’m strongly in favor of it.

    So everyone, please feel free to golf on this blog. Teach me some cool tricks and tighten up my code. :-)

    1Golf: Attempting write a program in the smallest number of keystrokes.

  17. sean said 21 days later:

    I guess every language’s golf reflects that language’s nature. One of the most interesting golf entries I’ve seen is this one by Ton Hospel

    http://groups.google.com/group/pl.comp.lang.perl/msg/3d47c84a1307e381?hl=en

    which to me reflects Perl’s focus on results over elegance and its appreciation of cleverness.

  18. Eric said 21 days later:

    Thanks! That’s pretty neat:

    But in “random” mappings with a very small result set like this, the shortest solution is often to make up some magic formula that has no particular meaning, but just happens to give the wanted result.

    The way he applies a “magic” function and strikes out irrelevant digits of the output reminds me of the prime game.

  19. Andy said 32 days later:

    With the any and all functions in Python 2.5, the conjoin and disjoin functions become a lot simpler:

    
    def conjoin(*preds):
        def conj(*args, **kwargs):
            return all(p(*args, **kwargs) for p in preds)
        return conj
    
    def disjoin(*preds):
        def disj(*args, **kwargs):
            return any(p(*args, **kwargs) for p in preds)
        return disj
    
    

    It doesn’t quite match the pretty point-free style though.

  20. Crest da Zoltral said 129 days later:

    Eric Kidd: Depending on how you count a Factor version is even shorter.

    : complement ( quot—quot ) \ not add ;

    : compose ( quot1 quot2—quot ) append ;

    : conjoin ( seq quot—? ) \ and add t swap reduce >boolean ;

    : disjoin ( seq quot—? ) \ or add f swap reduce >boolean ;

  21. Nicolás Sanguinetti said 361 days later:

    I like compose better as a member of the Proc class, if only because I find using ’*’ much more clear

    class Proc
      def compose(other)
        lambda {|*args| call(other.call(*args)) }
      end
    
      alias :* :compose
    end
    
    mult2_add1 = add1 * mult2
    

    Something similar (albeit a little more complicated) could be done for conjoin and &, so you could have

    is_even_number = is_even & is_number

    But it would be too much work when you can use Enumerable#all? :)

Comments are disabled