Composition is an incredibly useful technique in functional programming. I have been missing that in my development with Ruby, so I set out to implement it here.
In Haskell, you can write a function like:
-- f is a function that takes a value of type a -- and returns a value of type b f :: a -> b
We need some analogy with Ruby concepts. It doesn't appear to be methods, messages, or objects. Classes, however, seem to do it nicely.
- Replace the arrow with
ais the interface or duck that fits the single parameter of the class.
bis the interface/duck that fits the object produced by new.
So, we might express a class
F that implements message
b and expects an object responding to
class F attr_reader :b def initialize(x) @b = x.a end end
Next up, we want some class that implements the duck that
class G attr_reader :a def initialize(x) @a = x end end G.new(5).a # => 5 F.new(G.new(5)).b # => 5
This is class composition. But really, it'd be a lot nicer if we could write:
(F * G).new(5).b # => 5
Or, perhaps you prefer the bash-like pipe operator and reading your compositions from left to right. No problem:
(G | F).new(5).b # => 5
Naturally, this is quite a bit more interesting when your classes do something other than simply returning the value they were given.
In this example, the classes expect a parameter that duck-types
class Add5 def initialize(x) @value = x.value end def value @value + 5 end end class Multiply10 def initialize(x) @value = x.value end def value @value * 10 end end class Lift attr_reader :value def initialize(x) @value = x end end (Add5 * Multiply10 * Lift).new(7).value #=> 75 (Lift | Multiply10 | Add5).new(4).value #=> 45
If you'd prefer to compose classes directly, use
Mul10Add5 = Beethoven::Composer.new(Lift, Multiply10, Add5) Mul10Add5.new(5).value #=> 55
A more practical example is presented here
Add this line to your application's Gemfile:
And then execute:
Or install it yourself as:
$ gem install beethoven
- Fork it ( https://github.com/[my-github-username]/beethoven/fork )
- Create your feature branch (
git checkout -b my-new-feature)
- Commit your changes (
git commit -am 'Add some feature')
- Push to the branch (
git push origin my-new-feature)
- Create a new Pull Request