A gem for class composition in Ruby
Ruby
Switch branches/tags
Latest commit d841562 Aug 8, 2015 @parsonsmatt parsonsmatt Version bump
Permalink
Failed to load latest commit information.
lib Version bump Aug 9, 2015
spec Add Proc and move monkey patching to separate files Aug 9, 2015
.gitignore initial commit Jun 28, 2015
.rspec initial commit Jun 28, 2015
.ruby-gemset composable Jun 30, 2015
.ruby-version composable Jun 30, 2015
Gemfile initial commit Jun 28, 2015
Guardfile Add Proc and move monkey patching to separate files Aug 9, 2015
LICENSE.txt initial commit Jun 28, 2015
README.md
Rakefile Add Proc and move monkey patching to separate files Aug 9, 2015
beethoven.gemspec Add Proc and move monkey patching to separate files Aug 9, 2015

README.md

Beethoven

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.

  1. Replace the arrow with new
  2. a is the interface or duck that fits the single parameter of the class.
  3. b is 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 a as:

class F
  attr_reader :b

  def initialize(x)
    @b = x.a
  end
end

Next up, we want some class that implements the duck that F expects.

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 value.

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 Beethoven::Composer:

Mul10Add5 = Beethoven::Composer.new(Lift, Multiply10, Add5)
Mul10Add5.new(5).value
#=> 55

A more practical example is presented here

Installation

Add this line to your application's Gemfile:

gem 'beethoven'

And then execute:

$ bundle

Or install it yourself as:

$ gem install beethoven

Contributing

  1. Fork it ( https://github.com/[my-github-username]/beethoven/fork )
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create a new Pull Request