Skip to content

Instantly share code, notes, and snippets.

@d11wtq
Created March 25, 2013 02:16
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save d11wtq/5234515 to your computer and use it in GitHub Desktop.
Save d11wtq/5234515 to your computer and use it in GitHub Desktop.
Lists, and the concept of "head" and "tail", coupled with recursion are at the core of functional programming. This demonstrates how you can do it in Ruby, without ever writing a loop.
# foldl() is fundamental. With a fold, you can do everything else.
def foldl(list, acc, &fn)
if list == [] # base case, return the accumulator
acc
else
head, *tail = list
foldl(tail, fn.call(acc, head), &fn) #recurse on the remainder
end
end
# now we can write reverse()
def reverse(list)
foldl(list, []) {|acc, v| [v, *acc]}
end
# and we can write foldr (reverse foldl)
def foldr(list, acc, &fn)
foldl(reverse(list), acc, &fn)
end
# we can write map
def map(list, &fn)
reverse(foldl(list, []) {|acc, v| [fn.call(v), *acc]})
end
# we can write foreach
def foreach(list, &fn)
if list != []
head, *tail = list
fn.call(head)
foreach(tail, &fn)
end
end
# etc
foldl([1, 2, 3], 0) {|acc, n| acc + n} # => 6
reverse([1, 2, 3]) # => [3, 2, 1]
@matthayter
Copy link

Awesome!

Heh, Ruby doesn't include tail call elimination, might get some nice deep stacks :)

@d11wtq
Copy link
Author

d11wtq commented Mar 25, 2013

@matthayter yeah absolutely, but it was fun to observe the the splat really simplifies doing head|tail and cons operations in Ruby, without mutating data.

@matthayter
Copy link

@d11wtq Totally, those splats are awesome, I can't believe how clean that code is. I doubt it would be any more concise if written in Haskell.

@d11wtq
Copy link
Author

d11wtq commented Mar 25, 2013

@matthayter without pattern matching you have the annoying conditionals, which is slightly less clean than the haskell/erlang equivalent:

foldl(_, Acc, []) -> Acc;
foldl(Fn, Acc, [Head|Tail]) ->
  foldl(Fn, Fn(Acc, Head), Tail).

@pda
Copy link

pda commented Mar 25, 2013

RubyVM::InstructionSequence.compile_option = {
  tailcall_optimization: true,
  trace_instruction: false,
}

https://speakerdeck.com/jeg2/10-things-you-didnt-know-ruby-could-do?slide=46
(not really practical, only available in a sub-parser, I think.)

@d11wtq
Copy link
Author

d11wtq commented Mar 25, 2013

@pda wow, that's interesting. Presumably that will find its way into the default set of compile options then.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment