Project

beethoven

0.0
Repository is archived
No commit activity in last 3 years
No release in over 3 years
Functional programming is gaining more and more mindshare in software lately. One of the main benefits of programming in the functional style is function composition. Function composition allows you to break your program into small manageable chunks that can be put together in new and interesting ways. Object Oriented Programming is supposed to be composable, but the composition is lacking compared to FP. Perhaps Ruby's flexibility can get us part of the way there?
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
 Dependencies

Development

~> 1.7
~> 2.12
~> 10.0
~> 3.3
 Project Readme

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