Extra geeky: the recursive lambda

— June 20, 2008 at 11:06 PDT

I'm not sure where I first heard that you could do a recursive lamdba in Ruby, but it's been simmering on the back burner of my brain for a while. I've just never had a reason to use one, until now...

I wanted to process the Rails request params, which is a hash of strings and hashes of strings and hashes of strings and hashes... you get the idea. The need was to strip all the accent marks from user input throughout the application. Here's what I came up with:

class ApplicationController < ActionController::Base
  before_filter :strip_accents

  def strip_accents
    thunk = lambda do |key,value|
      case value
        when String then value.remove_accents!
        when Hash   then value.each(&thunk)

That's all completely clear, right? The filter enumerates the top-level hash using the &/to_proc operator to coerce the lambda to a block for the #each method. #each passes the key and value to the lambda, which either removes the accents from a string, or recursively enumerates the contents of a nested hash.

I think it's totally cool that you can do this in Ruby. Everyone thinks that Ruby is just an object-oriented language, but I like to think of it as the love-child of Smalltalk and LISP (with Miss Perl as the nanny).

15 commentsarcana, rails, ruby

  1. Adam Keys2008-06-20 11:35:47

    Totally awesome with the recursive lambda! And yeah, Ruby is just MatzLisp ;). However, I'd have to say Perl is the pervert uncle in the corner.

  2. Ian Bicking2008-06-20 14:26:15

    This is also a classic place to use the Y Combinator, avoiding the variable assignment. I just note it because this is the opportune time to figure that otherwise mysterious function.

  3. David Pisoni2008-06-20 15:03:30

    My memory of trying to do this in Perl was that it didn't work if the assignment (and definition of the lambda) was in the same statement with the declaration of the lexical variable being assigned to. In other words: my $thunk = sub { ... &$thunk ... }; # won't compile

    but: my $thunk; $thunk = sub { ... &$thunk ... }; # works

    Ruby does this better (both "procedurally" and semantically.)

  4. Carl Porth2008-06-20 15:04:22

    By passing before_filter a block you could also approach it like this:

    before_filter { strip_accents!(params) }
    def strip_accents!(hash_or_string)
      case hash_or_string
      when String then hash_or_string.remove_accents!
      when Hash   then hash_or_string.each { |key, value| strip_accents!(value) }
  5. Pat Nakajima2008-06-20 16:36:15

    I did something similar to this when I needed to be able to treat params like ActiveRecord objects (in that I wanted to be able to methodize the keys in order to use things like Object#try). Since params can come back as nested hashes and arrays, recursion saved my day. The snippet is on my blog here.

  6. Kevin Marshall2008-06-20 16:48:55

    Josh - the problem and solution is interesting, but what I just had to comment on was your view of what Ruby is...I LOVE IT! That has got to be one of the best things I've seen you come up with (and you come up with a lot of good stuff so that's saying something)...I'm def. going to be quoting you on that the next time I get a chance. :-D

  7. Josh Susser2008-06-20 19:05:22

    @Carl: Good alternative. I was shooting for a way to avoid having to define 2 methods, and that does it nicely too.

    @Ian: does Ruby have a Y combinator? I know #returning is a good K combinator (and the #tap variant in 1.9), but hadn't seen a Y around. Not that I'd really know what to do with a Y combinator...

  8. Giles Bowkett2008-06-20 23:47:32

    Archaeopteryx has had this for months. :-) Only way to get Arkx scheduling infinite streams of music without any threading/timing issues.

    You can do a Y combinator in Ruby.

    http://weblog.raganwald.com/2007/02/guest-blogger-tom-moertel-derives-y.html http://www.eecs.harvard.edu/~cduan/technical/ruby/ycombinator.shtml

  9. Phil2008-06-23 09:58:39

    @Josh: Another classic Y-combinator in Ruby: http://onestepback.org/index.cgi/Humor/InnovativeIdentification.red Not exactly useful, but fun.

    @Adam: dead on about Perl.

  10. F. Kaashoek 2008-06-23 10:04:47

    'Archaeopteryx has had this for months'

    Which is to say 'Practical Ruby Projects' has had this for months.

    Anyway, beautiful example John. It's elegant and pragmatic.

    Kids: eat your vegetables, drink your milk, don't do drugs, learn FP, and NEVER EVER put the Y-combinator in your ApplicationController.

  11. pato2008-06-23 11:42:28

    Here's yet another take on it:


    (including the "stack level too deep (SystemStackError)" feature) :-)

  12. Ron Phillips2008-06-25 03:44:44

    Excellent example, thanks for putting it up!

    I can see using this all kinds of ways.

  13. James Whiteman2008-06-26 16:18:37

    Nice! For everyone's enjoyment, I posted a version using the Y-combinator here:


  14. Gaius Novus2008-06-27 07:45:10

    Or you could write it like a regular old Ruby method with an optional argument that defaults to the whole params Hash:

    before_filter strip_accents
    def strip_accents(hash_or_string = nil)
      hash_or_string ||= params       # or self.params if you're into that
      case hash_or_string
      when String then hash_or_string.remove_accents!
      when Hash   then hash_or_string.each { |key, value| strip_accents(value) }
  15. Giles Bowkett2008-09-15 17:20:31

    Kaashoek - nice. No, Practical Ruby Projects didn't have it, and Archaeopteryx did.

Sorry, comments for this article are closed.