From 2fd5dd93daf4f6d67cf56882ad3bc8afc84aa2a9 Mon Sep 17 00:00:00 2001 From: Aanand Prasad Date: Sat, 12 Jan 2008 16:15:19 +0000 Subject: [PATCH] RSpec-ified --- README | 18 ++++++++ init.rb | 3 ++ lib/list.rb | 26 +++++++++++ lib/maybe.rb | 37 +++++++++++++++ monad.rb => lib/monad.rb | 118 ----------------------------------------------- test/specs.rb | 53 +++++++++++++++++++++ 6 files changed, 137 insertions(+), 118 deletions(-) create mode 100644 README create mode 100644 init.rb create mode 100644 lib/list.rb create mode 100644 lib/maybe.rb rename monad.rb => lib/monad.rb (51%) create mode 100644 test/specs.rb diff --git a/README b/README new file mode 100644 index 0000000..2d31079 --- /dev/null +++ b/README @@ -0,0 +1,18 @@ +Haskell-style monad do-notation for Ruby + +By Aanand Prasad (aanand.prasad@gmail.com) + +A first attempt, nervous and shaky. It is liable +to drop its cigarette in fear if you look it +too closely in the eye. + +Its biggest failing, and I don't see a way +out, is that you don't get lexical scope. ParseTree +is wonderful, but it can't work miracles. + +You can work around it, though, by passing in +stuff from the outside as arguments to `run', +and specifying those arguments on the block +you pass in as well. + +Examples at the bottom. diff --git a/init.rb b/init.rb new file mode 100644 index 0000000..4a3e9e5 --- /dev/null +++ b/init.rb @@ -0,0 +1,3 @@ +require 'lib/monad' +require 'lib/maybe' +require 'lib/list' diff --git a/lib/list.rb b/lib/list.rb new file mode 100644 index 0000000..bcd85cd --- /dev/null +++ b/lib/list.rb @@ -0,0 +1,26 @@ +class List < Monad + class << self + alias_method :list, :new + alias_method :unit, :list + end + + attr_accessor :array + + def initialize *args + @array = args + end + + def ==(l) + l.is_a? List and array == l.array + end + + def bind &f + lists = @array.map{ |x| f.call(x) } + + List.unit(*lists.inject([]) { |acc, l| acc + l.array }) + end + + def to_s + @array.inspect + end +end diff --git a/lib/maybe.rb b/lib/maybe.rb new file mode 100644 index 0000000..306ed2f --- /dev/null +++ b/lib/maybe.rb @@ -0,0 +1,37 @@ +class Maybe < Monad + class << self + alias_method :nothing, :new + alias_method :just, :new + alias_method :unit, :just + end + + attr_accessor :value, :nothing + + def initialize *args + if args.empty? + @nothing = true + else + @value = args.shift + end + end + + def ==(m) + m.is_a? Maybe and ((nothing and m.nothing) or (value = m.value)) + end + + def bind &f + if self.nothing + self + else + f.call(self.value) + end + end + + def to_s + if @nothing + "nothing" + else + "just(#{@value})" + end + end +end diff --git a/monad.rb b/lib/monad.rb similarity index 51% rename from monad.rb rename to lib/monad.rb index 618c587..2351963 100644 --- a/monad.rb +++ b/lib/monad.rb @@ -1,22 +1,3 @@ -# Haskell-style monad do-notation for Ruby -# -# By Aanand Prasad (aanand.prasad@gmail.com) -# -# A first attempt, nervous and shaky. It is liable -# to drop its cigarette in fear if you look it -# too closely in the eye. -# -# Its biggest failing, and I don't see a way -# out, is that you don't get lexical scope. ParseTree -# is wonderful, but it can't work miracles. -# -# You can work around it, though, by passing in -# stuff from the outside as arguments to `run', -# and specifying those arguments on the block -# you pass in as well. -# -# Examples at the bottom. - require 'rubygems' require 'parse_tree' require 'sexp_processor' @@ -99,102 +80,3 @@ class DoNotation < SexpProcessor end class DoNotationError < StandardError; end - -class Maybe < Monad - class << self - alias_method :nothing, :new - alias_method :just, :new - alias_method :unit, :just - end - - attr_accessor :value, :nothing - - def initialize *args - if args.empty? - @nothing = true - else - @value = args.shift - end - end - - def bind &f - if self.nothing - self - else - f.call(self.value) - end - end - - def to_s - if @nothing - "nothing" - else - "just(#{@value})" - end - end -end - -class List < Monad - class << self - alias_method :list, :new - alias_method :unit, :list - end - - attr_accessor :array - - def initialize *args - @array = args - end - - def bind &f - lists = @array.map{ |x| f.call(x) } - - List.unit(*lists.inject([]) { |acc, l| acc + l.array }) - end - - def to_s - @array.inspect - end -end - -# evaluates to nothing -maybe = Maybe.run do |m| - x =m just(1) - y =m nothing - - unit(x+y) -end - -puts maybe - -# evaluates to just(3) -maybe = Maybe.run do |m| - x =m just(1) - y =m just(2) - - unit(x+y) -end - -puts maybe - -# evaluates to [11, 21, 31, 12, 22, 32, 13, 23, 33] -list = List.run do |m| - x =m list(1,2,3) - y =m list(10,20,30) - - unit(x+y) -end - -puts list - -# evaluates to [111, 121, 131, 112, 122, 132, 113, 123, 133] -foo = 100 - -list = List.run(foo) do |m, foo| - x =m list(1,2,3) - y =m list(10,20,30) - - unit(x+y+foo) -end - -puts list diff --git a/test/specs.rb b/test/specs.rb new file mode 100644 index 0000000..aea79f3 --- /dev/null +++ b/test/specs.rb @@ -0,0 +1,53 @@ +require File.join(File.dirname(__FILE__), %w(.. init)) + +describe "Maybe:" do + specify "one or more `nothing's results in `nothing'" do + maybe = Maybe.run do |m| + x =m just(1) + y =m nothing + + unit(x+y) + end + + maybe.should == Maybe.nothing + end + + specify "all `just' results in `just'" do + maybe = Maybe.run do |m| + x =m just(1) + y =m just(2) + + unit(x+y) + end + + maybe.should == Maybe.just(3) + end +end + +describe "List:" do + specify "all results are calculated and concatenated" do + list = List.run do |m| + x =m list(1,2,3) + y =m list(10,20,30) + + unit(x+y) + end + + list.should == List.list(11, 21, 31, 12, 22, 32, 13, 23, 33) + end +end + +describe "Monad.run" do + specify "should pass extra arguments into the block" do + foo = 100 + + list = List.run(foo) do |m, foo| + x =m list(1,2,3) + y =m list(10,20,30) + + unit(x+y+foo) + end + + list.should == List.list(111, 121, 131, 112, 122, 132, 113, 123, 133) + end +end -- 2.11.4.GIT