Skip to content

Instantly share code, notes, and snippets.

@dchelimsky
Last active December 24, 2015 13:49
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save dchelimsky/6808471 to your computer and use it in GitHub Desktop.
Save dchelimsky/6808471 to your computer and use it in GitHub Desktop.
Prototype higher order function to compose lambdas in Ruby. Result of working with Matt Wynne on a means of wrapping filters in Cucumber (Ruby).
require 'wrong/adapters/rspec'
# (apply comp fns) in clojure
module Composer
def compose((*rest, last))
last ||= ->(x){x}
rest_reversed = rest.reverse_each
lambda do |*args|
rest_reversed.reduce(last[*args]) {|result, fn| fn[result]}
end
end
module_function :compose
end
describe Composer do
describe ".compose" do
it "returns identity with no fns" do
empty = Composer.compose([])
assert { empty.call(3) == 3 }
end
it "composes with 1 fns" do
double = ->(x) { x + x }
composed_double = Composer.compose([double])
assert { composed_double.call(3) == 6 }
end
it "composes with 2 fns" do
double = ->(x) { x + x }
square = ->(x) { x * x }
square_double = Composer.compose([square, double])
assert { square_double.call(3) == 36 }
end
it "composes with 3 fns" do
double = ->(x) { x * 2 }
triple = ->(x) { x * 3 }
square = ->(x) { x * x }
square_double_triple = Composer.compose([square, double, triple])
assert { square_double_triple.call(3) == 18*18 }
end
it "composes with no-arg fn at end" do
double = ->(x) { x * 2 }
triple = ->(x) { x * 3 }
square = ->(x) { x * x }
three = -> (){3}
square_double_triple = Composer.compose([square, double, triple, three])
assert { square_double_triple.call == 18*18 }
end
end
end
@tomstuart
Copy link

This is very elegant! If you wanted to avoid allocating all those procs at composition time, and doing all that splatting/unsplatting at call time, an alternative is to explicitly treat the last proc as variadic and just rfold #call across the rest:

def compose((*rest, last))
  last ||= -> x { x }

  lambda do |*args|
    rest.reverse.inject(last[*args]) { |result, fn| fn[result] }
  end
end

@tomstuart
Copy link

(Although it would be better to precompute rest.reverse before returning the lambda.)

@tomstuart
Copy link

Oh, and @threedaymonk wisely suggests achieving an rfold with reverse_each.inject instead of reverse.inject, which is better all round.

@mattwynne
Copy link

I didn't know about Facets. Facets contains a compose method on Proc, but nothing that works on a collection of procs like the clojure one does. I've refactored this code us use the Facets Proce#compose. It's simpler but it would still be nice to see it on an Array I think.

See https://gist.github.com/mattwynne/6811764

@dchelimsky
Copy link
Author

@tomstuart: updated with suggestions from you and @threedaymonk.

@mattwynne WDYT?

@tomstuart
Copy link

@dchelimsky Your changes don’t seem to incorporate @threedaymonk’s suggestion of using #reverse_each, which is preferable to #reverse since it doesn’t involve allocating a new array.

@dchelimsky
Copy link
Author

@tomstuart right you are - fixed

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