{}[http://travis-ci.org/ms-ati/rumonade]
= Rumonade[https://rubygems.org/gems/rumonade]
Project: github[http://github.com/ms-ati/rumonade]
Documentation: rubydoc.info[http://rubydoc.info/gems/rumonade/frames]
== A Ruby[http://www.ruby-lang.org] Monad[http://en.wikipedia.org/wiki/Monad_(functional_programming)] Library, Inspired by Scala[http://www.scala-lang.org]
Are you working in both the Scala[http://www.scala-lang.org] and Ruby[http://www.ruby-lang.org] worlds,
and finding that you miss some of the practical benefits of Scala's
monads[http://james-iry.blogspot.com/2007/09/monads-are-elephants-part-1.html] in Ruby?
Then Rumonade is for you.
The goal of this library is to make the most common and useful Scala monadic idioms available in Ruby via the following classes:
* {Rumonade::Option Option}
* {Rumonade::ArrayExtensions Array}
* {Rumonade::Either Either}
* Hash -- _coming_ _soon!_
* (_more_ _TBD_)
Syntactic support for scala-like for-comprehensions[http://www.scala-lang.org/node/111] will be implemented
as a sequence of calls to #flat_map, #select, etc, modelling Scala's
approach[http://stackoverflow.com/questions/3754089/scala-for-comprehension/3754568#3754568].
Support for an all_catch[http://www.scala-lang.org/archives/downloads/distrib/files/nightly/docs/library/scala/util/control/Exception$.html]
idiom will be implemented to turn blocks which might throw exceptions into Option or Either
results. If this proves useful (and a good fit for Ruby), then more narrow functional catchers can be implemented as well.
== Usage
==== {Rumonade::Option Option}: handle _possibly_ _nil_ values in a _functional_ fashion:
def format_date_in_march(time_or_date_or_nil)
Option(time_or_date_or_nil). # wraps possibly-nil value in an Option monad (Some or None)
map(&:to_date). # transforms a contained Time value into a Date value
select {|d| d.month == 3}. # filters out non-matching Date values (Some becomes None)
map(&:to_s). # transforms a contained Date value into a String value
map {|s| s.gsub('-', '')}. # transforms a contained String value by removing '-'
get_or_else("not in march!") # returns the contained value, or the alternative if None
end
format_date_in_march(nil) # => "not in march!"
format_date_in_march(Time.parse('2009-01-01 01:02')) # => "not in march!"
format_date_in_march(Time.parse('2011-03-21 12:34')) # => "20110321"
Note:
* each step of the chained computations above are functionally isolated
* the value can notionally _start_ as nil, or _become_ nil during a computation, without effecting any other chained computations
==== {Rumonade::Either Either}: handle failures ({Rumonade::Left Left}) and successes ({Rumonade::Right Right}) in a _functional_ fashion:
def find_person(name)
case name
when /Jack/i, /John/i
Right(name.capitalize)
else
Left("No such person: #{name.capitalize}")
end
end
# success looks like this:
find_person("Jack")
# => Right("Jack")
# failure looks like this:
find_person("Jill")
# => Left("No such person: Jill")
# on the 'happy path', we can functionally combine and transform successes:
(find_person("Jack").lift_to_a + find_person("John").lift_to_a).right.map { |*names| names.join(" and ") }
# => Right("Jack and John")
# while the failure cases are easily handled as well:
(find_person("Jack").lift_to_a +
find_person("John").lift_to_a +
find_person("Jill").lift_to_a +
find_person("Joan").lift_to_a).right.map { |*names| names.join(" and ") }
# => Left("No such person: Jill", "No such person: Joan")
(_more_ _examples_ _coming_ _soon_...)
== Approach
There have been many[http://moonbase.rydia.net/mental/writings/programming/monads-in-ruby/00introduction.html]
posts[http://pretheory.wordpress.com/2008/02/14/the-maybe-monad-in-ruby/]
and[http://www.valuedlessons.com/2008/01/monads-in-ruby-with-nice-syntax.html]
discussions[http://stackoverflow.com/questions/2709361/monad-equivalent-in-ruby]
about monads in Ruby, which have sparked a number of approaches.
Rumonade wants to be a practical drop-in Monad solution that will fit well into the Ruby world.
The priorities for Rumonade are:
1. Practical usability in day-to-day Ruby
* don't mess up normal idioms of the language (e.g., Hash#map)
* don't slow down normal idioms of the language (e.g., Array#map)
2. Rubyish-ness of usage
* Monad is a mix-in, requiring methods self.unit and #bind be implemented by target classes
* Prefer blocks to lambda/Procs where possible
3. Equivalent idioms to Scala where possible
== Status
Option, Either, and Array are complete.
Please try it out, and let me know what you think!