README.rdoc in rumonade-0.3.0 vs README.rdoc in rumonade-0.4.0

- old
+ new

@@ -1,12 +1,12 @@ {<img src="https://secure.travis-ci.org/ms-ati/rumonade.png?branch=master" alt="Build Status" />}[http://travis-ci.org/ms-ati/rumonade] = Rumonade[https://rubygems.org/gems/rumonade] -Project: github[http://github.com/ms-ati/rumonade] +*_Project_*: github[http://github.com/ms-ati/rumonade] -Documentation: rubydoc.info[http://rubydoc.info/gems/rumonade/frames] +*_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 @@ -15,24 +15,24 @@ 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!_ +* {Rumonade::Hash Hash} * (_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 +as a sequence of calls to #flat_map, #select, etc, modeling 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 +== Usage Examples -==== {Rumonade::Option Option}: handle _possibly_ _nil_ values in a _functional_ fashion: +=== {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) @@ -47,11 +47,12 @@ 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: +--- +=== {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) @@ -66,21 +67,44 @@ # 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 ") } + # lift the contained values into Array, in order to combine them: + find_person("Joan").lift_to_a + # => Left(["No such person: Joan"]) + + # on the 'happy path', combine and transform successes into a single success result: + (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: + # but if there were errors, we still have a Left with all the errors inside: (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") + # => Left(["No such person: Jill", "No such person: Joan"]) + # equivalent to the previous example, but shorter: + %w(Jack John Jill Joan).map { |nm| find_person(nm).lift_to_a }.inject(:+). + right.map { |*names| names.join(" and ") } + # => Left(["No such person: Jill", "No such person: Joan"]) + +--- +=== {Rumonade::Hash Hash}: + + h = { "Foo" => 1, "Bar" => 2, "Baz" => 3 } + h = h.flat_map { |k, v| { k => v, k.upcase => v * 10 } } + # => {"Foo"=>1, "FOO"=>10, "Bar"=>2, "BAR"=>20, "Baz"=>3, "BAZ"=>30} + h = h.select { |k, v| k =~ /^b/i } + # => {"Bar"=>2, "BAR"=>20, "Baz"=>3, "BAZ"=>30} + h.get("Bar") + # => Some(2) + h.get("Foo") + # => None + (_more_ _examples_ _coming_ _soon_...) == Approach There have been many[http://moonbase.rydia.net/mental/writings/programming/monads-in-ruby/00introduction.html] @@ -94,13 +118,16 @@ The priorities for Rumonade are: 1. Practical usability in day-to-day Ruby * <b>don't</b> mess up normal idioms of the language (e.g., Hash#map) * <b>don't</b> 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 + * Monad is a mix-in, requiring methods +self.unit+ and +#bind+ be implemented by target classes + * Prefer blocks to lambda/Procs where possible, but allow both 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! +Option, Either, Array, and Hash are already usable. + +<b><em>Supported Ruby versions</em></b>: MRI 1.9.2, MRI 1.9.3, JRuby in 1.9 mode, and Rubinius in 1.9 mode. + +Please try it out, and let me know what you think! Suggestions are always welcome.