aanand-ruby-do-notation 0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,15 @@
1
+ lib/do_notation/monad.rb
2
+ lib/do_notation/monad_plus.rb
3
+ lib/do_notation/monads/array.rb
4
+ lib/do_notation/monads/maybe.rb
5
+ lib/do_notation/monads/simulations.rb
6
+ lib/do_notation/rewriter.rb
7
+ lib/do_notation.rb
8
+ README.markdown
9
+ test/array.rb
10
+ test/maybe.rb
11
+ test/monad_plus.rb
12
+ test/simulations.rb
13
+ test/spec_helper.rb
14
+ test/specs.rb
15
+ Manifest
@@ -0,0 +1,34 @@
1
+ Haskell-style monad do-notation for Ruby
2
+ ========================================
3
+
4
+ Example:
5
+
6
+ class Array
7
+ include Monad
8
+
9
+ def self.unit x
10
+ [x]
11
+ end
12
+
13
+ def bind &f
14
+ map(&f).inject([]){ |a,b| a+b }
15
+ end
16
+ end
17
+
18
+ Array.run do
19
+ x <- ["first", "second"]
20
+ y <- ["once", "twice"]
21
+
22
+ unit("#{x} cousin #{y} removed")
23
+ end
24
+
25
+ The above code returns the array:
26
+
27
+ ["first cousin once removed",
28
+ "first cousin twice removed",
29
+ "second cousin once removed",
30
+ "second cousin twice removed"]
31
+
32
+ For more examples, see the test suite.
33
+
34
+ By Aanand Prasad (aanand.prasad@gmail.com)
@@ -0,0 +1,9 @@
1
+ $: << File.dirname(__FILE__)
2
+
3
+ require 'rubygems'
4
+ require 'parse_tree'
5
+ require 'sexp_processor'
6
+ require 'ruby2ruby'
7
+
8
+ require 'do_notation/rewriter'
9
+ require 'do_notation/monad'
@@ -0,0 +1,24 @@
1
+ module Monad
2
+ module ClassMethods
3
+ def run &block
4
+ eval(ruby_for(block), block).call
5
+ end
6
+
7
+ def ruby_for block
8
+ @cached_ruby ||= {}
9
+ @cached_ruby[block.to_s] ||= "#{self.name}.instance_eval { #{Ruby2Ruby.new.process(Rewriter.new.process(block.to_method.to_sexp)[2])} }"
10
+ end
11
+ end
12
+
13
+ def bind_const &block
14
+ bind { |_| block.call() }
15
+ end
16
+
17
+ def >> n
18
+ bind_const { n }
19
+ end
20
+
21
+ def self.included m
22
+ m.extend ClassMethods
23
+ end
24
+ end
@@ -0,0 +1,9 @@
1
+ module MonadPlus
2
+ def guard p
3
+ if p
4
+ unit(mzero)
5
+ else
6
+ mzero
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,21 @@
1
+ require 'do_notation/monad_plus'
2
+
3
+ class Array
4
+ include Monad
5
+
6
+ def self.unit x
7
+ [x]
8
+ end
9
+
10
+ def bind &f
11
+ map(&f).inject([]){ |a,b| a+b }
12
+ end
13
+
14
+ extend MonadPlus
15
+
16
+ def self.mzero
17
+ []
18
+ end
19
+
20
+ alias_method :mplus, :+
21
+ end
@@ -0,0 +1,31 @@
1
+ require 'do_notation/monad_plus'
2
+
3
+ class Maybe < Struct.new(:value)
4
+ include Monad
5
+
6
+ def self.unit value
7
+ self.new(value)
8
+ end
9
+
10
+ def bind &f
11
+ if value.nil?
12
+ self
13
+ else
14
+ f.call(value)
15
+ end
16
+ end
17
+
18
+ extend MonadPlus
19
+
20
+ def self.mzero
21
+ unit(nil)
22
+ end
23
+
24
+ def mplus m
25
+ if value.nil?
26
+ m
27
+ else
28
+ self
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,117 @@
1
+ # converted from Mauricio Fernandez's "Warm fuzzy things for random simulations":
2
+ # http://eigenclass.org/hiki/warm-fuzzy-things-for-random-simulations
3
+ # http://eigenclass.org/hiki.rb?c=plugin;plugin=attach_download;p=warm-fuzzy-things-for-random-simulations;file_name=fuzzy-warm-simulations.rb
4
+
5
+ module PRNG
6
+ def next_state(s); (69069 * s + 5) % (2**32) end
7
+ end
8
+
9
+ class Simulation
10
+ extend PRNG
11
+ include Monad
12
+
13
+ attr_reader :f
14
+
15
+ def initialize(&b)
16
+ @f = b
17
+ end
18
+
19
+ def self.unit(x)
20
+ new { |s| [x, s] }
21
+ end
22
+
23
+ def self.rand(n)
24
+ self.new do |s|
25
+ [s.abs % n, next_state(s)]
26
+ end
27
+ end
28
+
29
+ def bind(&b)
30
+ self.class.new do |s|
31
+ x, s = @f.call(s)
32
+ b.call(x).f.call(s)
33
+ end
34
+ end
35
+
36
+ def play(s = 12345)
37
+ @f.call(s).first
38
+ end
39
+ end
40
+
41
+ class Distribution
42
+ extend PRNG
43
+ include Monad
44
+
45
+ attr_reader :a
46
+
47
+ def initialize(a)
48
+ @a = a
49
+ end
50
+
51
+ def self.wrap(a)
52
+ new(a)
53
+ end
54
+
55
+ def self.unit(x)
56
+ wrap [[x, 1.0]]
57
+ end
58
+
59
+ def self.rand(n)
60
+ p = 1.0 / n
61
+ new((0...n).map{|i| [i, p]})
62
+ end
63
+
64
+ def bind(&b)
65
+ if @a.empty?
66
+ self.class.wrap([])
67
+ else
68
+ x, p = @a[0]
69
+ self.class.wrap(mulp(p, b.call(x)) + self.class.new(@a[1..-1]).bind(&b).a)
70
+ end
71
+ end
72
+
73
+ def play
74
+ h = Hash.new{|h, k| h[k] = 0.0}
75
+ @a.each{|x, p| h[x] += p}
76
+ h.to_a.sort_by{|x,| x}
77
+ end
78
+
79
+ private
80
+ def mulp(p, l)
81
+ l.a.map{|x, p1| [x, p * p1]}
82
+ end
83
+ end
84
+
85
+ class Expectation
86
+ extend PRNG
87
+ include Monad
88
+
89
+ attr_reader :f
90
+
91
+ def initialize(&b)
92
+ @f = b
93
+ end
94
+
95
+ def self.wrap(&proc)
96
+ new(&proc)
97
+ end
98
+
99
+ def self.unit(x)
100
+ wrap{ |f| f.call(x) }
101
+ end
102
+
103
+ def self.rand(n)
104
+ wrap do |k|
105
+ sum = (0..n-1).map{|x| k.call(x)}.inject{|s,x| s+x}
106
+ 1.0 * sum / n
107
+ end
108
+ end
109
+
110
+ def bind(&b)
111
+ self.class.wrap{|k| @f.call(lambda{|x| b.call(x).f.call(k)}) }
112
+ end
113
+
114
+ def play(x)
115
+ @f.call(lambda{|x1| x1 == x ? 1.0 : 0.0})
116
+ end
117
+ end
@@ -0,0 +1,63 @@
1
+ class Rewriter < SexpProcessor
2
+ def process_bmethod exp
3
+ type = exp.shift
4
+
5
+ exp.shift # throw away arguments
6
+
7
+ body = process(exp.shift)
8
+
9
+ if body[0] == :block
10
+ body.shift
11
+ else
12
+ body = s([*body])
13
+ end
14
+
15
+ s(:iter, s(:fcall, :proc), nil, *rewrite_assignments(body))
16
+ end
17
+
18
+ def rewrite_assignments exp
19
+ return [] if exp.empty?
20
+
21
+ head = exp.shift
22
+
23
+ if head.first == :call and head[1].first == :vcall and head[2] == :< and head[3].first == :array and head[3][1].last == :-@
24
+ var_name = head[1][1]
25
+ expression = head[3][1][1]
26
+
27
+ body = rewrite_assignments(exp)
28
+
29
+ if body.first.is_a? Symbol
30
+ body = [s(*body)]
31
+ end
32
+
33
+ [s(:iter,
34
+ s(:call, process(expression), :bind),
35
+ s(:dasgn_curr, var_name),
36
+ *body)]
37
+ elsif exp.empty?
38
+ [process(head)]
39
+ else
40
+ [s(:iter, s(:call, process(head) , :bind_const), nil ,
41
+ *rewrite_assignments(exp)) ]
42
+ end
43
+ end
44
+
45
+ def self.pp(obj, indent='')
46
+ return obj.inspect unless obj.is_a? Array
47
+ return '()' if obj.empty?
48
+
49
+ str = '(' + pp(obj.first, indent + ' ')
50
+
51
+ if obj.length > 1
52
+ str << ' '
53
+
54
+ next_indent = indent + (' ' * str.length)
55
+
56
+ str << obj[1..-1].map{ |o| pp(o, next_indent) }.join("\n#{next_indent}")
57
+ end
58
+
59
+ str << ')'
60
+
61
+ str
62
+ end
63
+ end
@@ -0,0 +1,59 @@
1
+
2
+ # Gem::Specification for Ruby-do-notation-0.1
3
+ # Originally generated by Echoe
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = %q{ruby-do-notation}
7
+ s.version = "0.1"
8
+
9
+ s.specification_version = 2 if s.respond_to? :specification_version=
10
+
11
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
12
+ s.authors = ["Aanand Prasad"]
13
+ s.date = %q{2008-06-20}
14
+ s.description = %q{Haskell-style monad do-notation for Ruby}
15
+ s.email = %q{aanand.prasad@gmail.com}
16
+ s.extra_rdoc_files = ["lib/do_notation/monad.rb", "lib/do_notation/monad_plus.rb", "lib/do_notation/monads/array.rb", "lib/do_notation/monads/maybe.rb", "lib/do_notation/monads/simulations.rb", "lib/do_notation/rewriter.rb", "lib/do_notation.rb", "README.markdown"]
17
+ s.files = ["lib/do_notation/monad.rb", "lib/do_notation/monad_plus.rb", "lib/do_notation/monads/array.rb", "lib/do_notation/monads/maybe.rb", "lib/do_notation/monads/simulations.rb", "lib/do_notation/rewriter.rb", "lib/do_notation.rb", "README.markdown", "test/array.rb", "test/maybe.rb", "test/monad_plus.rb", "test/simulations.rb", "test/spec_helper.rb", "test/specs.rb", "Manifest", "ruby-do-notation.gemspec"]
18
+ s.has_rdoc = true
19
+ s.homepage = %q{http://github.com/aanand/ruby-do-notation/tree/master}
20
+ s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Ruby-do-notation", "--main", "README.markdown"]
21
+ s.require_paths = ["lib"]
22
+ s.rubyforge_project = %q{ruby-do-notation}
23
+ s.rubygems_version = %q{1.1.0}
24
+ s.summary = %q{Haskell-style monad do-notation for Ruby}
25
+
26
+ s.add_dependency(%q<ParseTree>, [">= 0"])
27
+ s.add_dependency(%q<ruby2ruby>, [">= 0"])
28
+ end
29
+
30
+
31
+ # # Original Rakefile source (requires the Echoe gem):
32
+ #
33
+ # require 'spec/rake/spectask'
34
+ #
35
+ # spec_list = FileList['test/*.rb']
36
+ #
37
+ # task :default => :test
38
+ #
39
+ # desc "Run all specs"
40
+ # Spec::Rake::SpecTask.new('spec') do |t|
41
+ # t.spec_files = spec_list
42
+ # end
43
+ #
44
+ # desc "Run all specs with RCov"
45
+ # Spec::Rake::SpecTask.new('rcov') do |t|
46
+ # t.spec_files = spec_list
47
+ # t.rcov = true
48
+ # end
49
+ #
50
+ # require 'echoe'
51
+ #
52
+ # Echoe.new('ruby-do-notation') do |p|
53
+ # p.author = 'Aanand Prasad'
54
+ # p.summary = 'Haskell-style monad do-notation for Ruby'
55
+ # p.email = 'aanand.prasad@gmail.com'
56
+ # p.url = 'http://github.com/aanand/ruby-do-notation/tree/master'
57
+ # p.version = '0.1'
58
+ # p.dependencies = ['ParseTree', 'ruby2ruby']
59
+ # end
@@ -0,0 +1,18 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+ require 'do_notation/monads/array'
3
+
4
+ describe "Array:" do
5
+ specify "all results are calculated and concatenated" do
6
+ array = Array.run do
7
+ x <- ["first", "second"]
8
+ y <- ["once", "twice"]
9
+
10
+ unit("#{x} cousin #{y} removed")
11
+ end
12
+
13
+ array.should == ["first cousin once removed",
14
+ "first cousin twice removed",
15
+ "second cousin once removed",
16
+ "second cousin twice removed"]
17
+ end
18
+ end
@@ -0,0 +1,26 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+ require 'do_notation/monads/maybe'
3
+
4
+ describe "Maybe:" do
5
+ specify "one or more nils results in nil" do
6
+ maybe = Maybe.run do
7
+ x <- unit(1)
8
+ y <- unit(nil)
9
+
10
+ unit(x+y)
11
+ end
12
+
13
+ maybe.value.should == nil
14
+ end
15
+
16
+ specify "all non-nil results in complete calculation" do
17
+ maybe = Maybe.run do
18
+ x <- unit(1)
19
+ y <- unit(2)
20
+
21
+ unit(x+y)
22
+ end
23
+
24
+ maybe.value.should == 3
25
+ end
26
+ end
@@ -0,0 +1,37 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+ require 'do_notation/monads/array'
3
+ require 'do_notation/monads/maybe'
4
+
5
+ describe "MonadPlus:" do
6
+ specify "mzero >>= f = mzero" do
7
+ Array.mzero.bind{ |x| unit(x+1) }.should == Array.mzero
8
+ Maybe.mzero.bind{ |x| unit(x+1) }.should == Maybe.mzero
9
+ end
10
+
11
+ specify "v >> mzero = mzero" do
12
+ ([1,2,3] >> Array.mzero).should == Array.mzero
13
+ (Maybe.unit(1) >> Maybe.mzero).should == Maybe.mzero
14
+ end
15
+
16
+ specify "mzero `mplus` m = m" do
17
+ Array.mzero.mplus([1,2,3]).should == [1,2,3]
18
+ Maybe.mzero.mplus(Maybe.unit(1)).should == Maybe.unit(1)
19
+ end
20
+
21
+ specify "m `mplus` mzero = m" do
22
+ [1,2,3].mplus(Array.mzero).should == [1,2,3]
23
+ Maybe.unit(1).mplus(Maybe.mzero).should == Maybe.unit(1)
24
+ end
25
+
26
+ specify "guard() prunes the execution tree" do
27
+ array = Array.run do
28
+ x <- [0,1,2,3]
29
+ y <- [0,1,2,3]
30
+ guard(x + y == 3)
31
+
32
+ unit([x,y])
33
+ end
34
+
35
+ array.should == [[0, 3], [1, 2], [2, 1], [3, 0]]
36
+ end
37
+ end
@@ -0,0 +1,46 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+ require 'do_notation/monads/simulations'
3
+
4
+ roll_3d6 = proc do
5
+ d1 <- rand(6)
6
+ d2 <- rand(6)
7
+ d3 <- rand(6)
8
+
9
+ unit(1+d1 + 1+d2 + 1+d3)
10
+ end
11
+
12
+ roll_3d6_b = proc do
13
+ d1 <- rand(6)
14
+ d2 <- rand(6)
15
+ d3 <- rand(6)
16
+
17
+ if [d1, d2, d3].include?(5)
18
+ run do
19
+ d4 <- rand(6)
20
+ unit(1+d1 + 1+d2 + 1+d3 + 1+d4)
21
+ end
22
+ else
23
+ unit(1+d1 + 1+d2 + 1+d3)
24
+ end
25
+ end
26
+
27
+ describe "Simulation" do
28
+ specify "generates correct answers" do
29
+ Simulation.run(&roll_3d6).play.should == 9
30
+ Simulation.run(&roll_3d6_b).play.should == 9
31
+ end
32
+ end
33
+
34
+ describe "Distribution" do
35
+ specify "generates correct answers" do
36
+ Distribution.run(&roll_3d6).play.inspect.should == "[[3, 0.00462962962962963], [4, 0.0138888888888889], [5, 0.0277777777777778], [6, 0.0462962962962963], [7, 0.0694444444444444], [8, 0.0972222222222222], [9, 0.115740740740741], [10, 0.125], [11, 0.125], [12, 0.115740740740741], [13, 0.0972222222222222], [14, 0.0694444444444444], [15, 0.0462962962962963], [16, 0.0277777777777778], [17, 0.0138888888888889], [18, 0.00462962962962963]]"
37
+ Distribution.run(&roll_3d6_b).play.inspect.should == "[[3, 0.00462962962962963], [4, 0.0138888888888889], [5, 0.0277777777777778], [6, 0.0462962962962963], [7, 0.0694444444444444], [8, 0.0833333333333333], [9, 0.0902777777777777], [10, 0.0902777777777778], [11, 0.0833333333333333], [12, 0.0694444444444445], [13, 0.0625000000000001], [14, 0.0601851851851853], [15, 0.0578703703703705], [16, 0.0555555555555557], [17, 0.0532407407407408], [18, 0.0462962962962964], [19, 0.0354938271604938], [20, 0.0239197530864198], [21, 0.0146604938271605], [22, 0.00771604938271605], [23, 0.00308641975308642], [24, 0.000771604938271605]]"
38
+ end
39
+ end
40
+
41
+ describe "Expectation" do
42
+ specify "generates correct answers" do
43
+ (1..21).collect { |x| Expectation.run(&roll_3d6).play(x) }.inspect.should == "[0.0, 0.0, 0.00462962962962963, 0.0138888888888889, 0.0277777777777778, 0.0462962962962963, 0.0694444444444444, 0.0972222222222222, 0.115740740740741, 0.125, 0.125, 0.115740740740741, 0.0972222222222222, 0.0694444444444444, 0.0462962962962963, 0.0277777777777778, 0.0138888888888889, 0.00462962962962963, 0.0, 0.0, 0.0]"
44
+ (1..21).collect { |x| Expectation.run(&roll_3d6_b).play(x) }.inspect.should == "[0.0, 0.0, 0.00462962962962963, 0.0138888888888889, 0.0277777777777778, 0.0462962962962963, 0.0694444444444444, 0.0833333333333333, 0.0902777777777778, 0.0902777777777777, 0.0833333333333333, 0.0694444444444444, 0.0625, 0.0601851851851852, 0.0578703703703704, 0.0555555555555556, 0.0532407407407407, 0.0462962962962963, 0.0354938271604938, 0.0239197530864197, 0.0146604938271605]"
45
+ end
46
+ end
@@ -0,0 +1 @@
1
+ require File.join(File.dirname(__FILE__), %w(.. lib do_notation))
@@ -0,0 +1,66 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+ require 'do_notation/monads/array'
3
+
4
+ describe "Monad.run" do
5
+ specify "should have lexical scope" do
6
+ foo = "cousin"
7
+ bar = "removed"
8
+
9
+ array = Array.run do
10
+ x <- ["first", "second"]
11
+ y <- ["once", "twice"]
12
+
13
+ unit("#{x} #{foo} #{y} #{bar}")
14
+ end
15
+
16
+ array.should == ["first cousin once removed",
17
+ "first cousin twice removed",
18
+ "second cousin once removed",
19
+ "second cousin twice removed"]
20
+ end
21
+
22
+ specify "should be nestable" do
23
+ array = Array.run do
24
+ x <- run do
25
+ a <- ['A','a']
26
+ b <- ['B','b']
27
+
28
+ unit(a+b)
29
+ end
30
+
31
+ y <- run do
32
+ c <- ['C','c']
33
+ d <- ['D','d']
34
+
35
+ unit(c+d)
36
+ end
37
+
38
+ unit(x+y)
39
+ end
40
+
41
+ array.should == ["ABCD", "ABCd", "ABcD", "ABcd",
42
+ "AbCD", "AbCd", "AbcD", "Abcd",
43
+ "aBCD", "aBCd", "aBcD", "aBcd",
44
+ "abCD", "abCd", "abcD", "abcd"]
45
+ end
46
+ end
47
+
48
+ describe 'Monad#>>' do
49
+ specify "should compose two values, discarding the first" do
50
+ ([1] >> [2]).should == [2]
51
+ end
52
+ end
53
+
54
+ describe "Rewriter.pp" do
55
+ specify "should produce correct output" do
56
+ array = [:a, [:b],
57
+ [:c, :d,
58
+ :e]]
59
+
60
+ Rewriter.pp(array).should == <<CODE.strip
61
+ (:a (:b)
62
+ (:c :d
63
+ :e))
64
+ CODE
65
+ end
66
+ end
metadata ADDED
@@ -0,0 +1,97 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: aanand-ruby-do-notation
3
+ version: !ruby/object:Gem::Version
4
+ version: "0.1"
5
+ platform: ruby
6
+ authors:
7
+ - Aanand Prasad
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-06-20 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: ParseTree
17
+ version_requirement:
18
+ version_requirements: !ruby/object:Gem::Requirement
19
+ requirements:
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: "0"
23
+ version:
24
+ - !ruby/object:Gem::Dependency
25
+ name: ruby2ruby
26
+ version_requirement:
27
+ version_requirements: !ruby/object:Gem::Requirement
28
+ requirements:
29
+ - - ">="
30
+ - !ruby/object:Gem::Version
31
+ version: "0"
32
+ version:
33
+ description: Haskell-style monad do-notation for Ruby
34
+ email: aanand.prasad@gmail.com
35
+ executables: []
36
+
37
+ extensions: []
38
+
39
+ extra_rdoc_files:
40
+ - lib/do_notation/monad.rb
41
+ - lib/do_notation/monad_plus.rb
42
+ - lib/do_notation/monads/array.rb
43
+ - lib/do_notation/monads/maybe.rb
44
+ - lib/do_notation/monads/simulations.rb
45
+ - lib/do_notation/rewriter.rb
46
+ - lib/do_notation.rb
47
+ - README.markdown
48
+ files:
49
+ - lib/do_notation/monad.rb
50
+ - lib/do_notation/monad_plus.rb
51
+ - lib/do_notation/monads/array.rb
52
+ - lib/do_notation/monads/maybe.rb
53
+ - lib/do_notation/monads/simulations.rb
54
+ - lib/do_notation/rewriter.rb
55
+ - lib/do_notation.rb
56
+ - README.markdown
57
+ - test/array.rb
58
+ - test/maybe.rb
59
+ - test/monad_plus.rb
60
+ - test/simulations.rb
61
+ - test/spec_helper.rb
62
+ - test/specs.rb
63
+ - Manifest
64
+ - ruby-do-notation.gemspec
65
+ has_rdoc: true
66
+ homepage: http://github.com/aanand/ruby-do-notation/tree/master
67
+ post_install_message:
68
+ rdoc_options:
69
+ - --line-numbers
70
+ - --inline-source
71
+ - --title
72
+ - Ruby-do-notation
73
+ - --main
74
+ - README.markdown
75
+ require_paths:
76
+ - lib
77
+ required_ruby_version: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: "0"
82
+ version:
83
+ required_rubygems_version: !ruby/object:Gem::Requirement
84
+ requirements:
85
+ - - ">="
86
+ - !ruby/object:Gem::Version
87
+ version: "0"
88
+ version:
89
+ requirements: []
90
+
91
+ rubyforge_project: ruby-do-notation
92
+ rubygems_version: 1.0.1
93
+ signing_key:
94
+ specification_version: 2
95
+ summary: Haskell-style monad do-notation for Ruby
96
+ test_files: []
97
+