do_notation 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/.gitignore CHANGED
@@ -3,3 +3,4 @@ coverage
3
3
  doc
4
4
  *.tmproj
5
5
  *.gem
6
+ Gemfile.lock
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --colour
data/.travis.yml ADDED
@@ -0,0 +1,5 @@
1
+ rvm:
2
+ - 1.8.7
3
+ - 1.9.2
4
+ - 1.9.3
5
+
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source :rubygems
2
+ gemspec
3
+
@@ -1,4 +1,5 @@
1
- = Haskell-style monad do-notation for Ruby
1
+ Haskell-style monad do-notation for Ruby
2
+ ========================================
2
3
 
3
4
  Example:
4
5
 
@@ -6,20 +7,20 @@ Example:
6
7
 
7
8
  class Array
8
9
  include Monad
9
-
10
+
10
11
  def self.unit x
11
12
  [x]
12
13
  end
13
-
14
+
14
15
  def bind &f
15
- map(&f).inject([]){ |a,b| a+b }
16
+ map(&f).inject([], &:+)
16
17
  end
17
18
  end
18
-
19
+
19
20
  Array.run do
20
21
  x <- ["first", "second"]
21
22
  y <- ["once", "twice"]
22
-
23
+
23
24
  unit("#{x} cousin #{y} removed")
24
25
  end
25
26
 
@@ -32,11 +33,9 @@ The above code returns the array:
32
33
 
33
34
  For more examples, see the test suite.
34
35
 
35
- == Installation
36
-
37
- There's a gem hosted on Github, so follow the setup instructions
38
- at http://gems.github.com and then run:
36
+ Installation
37
+ ============
39
38
 
40
- sudo gem install aanand-ruby-do-notation
39
+ gem install do_notation
41
40
 
42
41
  By Aanand Prasad (aanand.prasad@gmail.com)
data/Rakefile CHANGED
@@ -1,32 +1,11 @@
1
- require 'spec/rake/spectask'
2
-
3
- spec_list = FileList['test/*.rb']
1
+ require 'rspec/core/rake_task'
4
2
 
5
3
  task :default => :spec
6
4
 
7
5
  desc "Run all specs"
8
- Spec::Rake::SpecTask.new('spec') do |t|
9
- t.spec_files = spec_list
10
- end
6
+ RSpec::Core::RakeTask.new('spec')
11
7
 
12
8
  desc "Run all specs with RCov"
13
- Spec::Rake::SpecTask.new('rcov') do |t|
14
- t.spec_files = spec_list
9
+ RSpec::Core::RakeTask.new('rcov') do |t|
15
10
  t.rcov = true
16
11
  end
17
-
18
- begin
19
- require 'jeweler'
20
- Jeweler::Tasks.new do |gemspec|
21
- gemspec.name = "do_notation"
22
- gemspec.summary = 'Haskell-style monad do-notation for Ruby'
23
- gemspec.description = 'Haskell-style monad do-notation for Ruby'
24
- gemspec.email = 'aanand.prasad@gmail.com'
25
- gemspec.homepage = 'http://github.com/aanand/do_notation'
26
- gemspec.authors = ["Aanand Prasad"]
27
- gemspec.add_dependency('ParseTree')
28
- gemspec.add_dependency('ruby2ruby', '1.1.9')
29
- end
30
- rescue LoadError
31
- puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
32
- end
@@ -0,0 +1,14 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = "do_notation"
3
+ s.version = "0.3.0"
4
+ s.summary = 'Haskell-style monad do-notation for Ruby'
5
+ s.email = 'aanand.prasad@gmail.com'
6
+ s.homepage = 'https://github.com/aanand/do_notation'
7
+ s.authors = ["Aanand Prasad"]
8
+
9
+ s.files = `git ls-files`.split
10
+
11
+ s.add_dependency('sourcify')
12
+ s.add_development_dependency('rake')
13
+ s.add_development_dependency('rspec', '~> 2.0')
14
+ end
data/lib/do_notation.rb CHANGED
@@ -1,12 +1,39 @@
1
- $: << File.dirname(__FILE__)
1
+ require 'sourcify'
2
2
 
3
- require 'rubygems'
3
+ module DoNotation
4
+ def self.run(klass, &block)
5
+ eval(ruby_for(klass, block), block.binding).call
6
+ end
4
7
 
5
- gem 'ruby2ruby', '1.1.9'
8
+ def self.ruby_for(klass, block)
9
+ "#{klass.name}.instance_eval { #{ruby_for_block(block)} }"
10
+ end
6
11
 
7
- require 'parse_tree'
8
- require 'sexp_processor'
9
- require 'ruby2ruby'
12
+ def self.ruby_for_block(block)
13
+ @cached_ruby ||= {}
14
+ @cached_ruby[block.to_s] ||= Ruby2Ruby.new.process(DoNotation::Rewriter.new.process(block.to_sexp))
15
+ end
16
+
17
+ def self.pp(obj, indent='')
18
+ return obj.inspect unless obj.is_a? Array
19
+ return '()' if obj.empty?
20
+
21
+ str = '(' + pp(obj.first, indent + ' ')
22
+
23
+ if obj.length > 1
24
+ str << ' '
25
+
26
+ next_indent = indent + (' ' * str.length)
27
+
28
+ str << obj[1..-1].map{ |o| pp(o, next_indent) }.join("\n#{next_indent}")
29
+ end
30
+
31
+ str << ')'
32
+
33
+ str
34
+ end
35
+ end
10
36
 
11
37
  require 'do_notation/rewriter'
12
38
  require 'do_notation/monad'
39
+
@@ -1,24 +1,13 @@
1
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() }
2
+ def run &block
3
+ DoNotation.run(self, &block)
15
4
  end
16
5
 
17
- def >> n
18
- bind_const { n }
6
+ def bind_const value, &block
7
+ bind(value) { block.call }
19
8
  end
20
9
 
21
- def self.included m
22
- m.extend ClassMethods
10
+ def compose a, b
11
+ bind_const(a) { b }
23
12
  end
24
13
  end
@@ -1,21 +1,24 @@
1
+ require 'do_notation'
1
2
  require 'do_notation/monad_plus'
2
3
 
3
4
  class Array
4
- include Monad
5
+ extend Monad
5
6
 
6
7
  def self.unit x
7
8
  [x]
8
9
  end
9
-
10
- def bind &f
11
- map(&f).inject([]){ |a,b| a+b }
10
+
11
+ def self.bind value, &f
12
+ value.map(&f).inject([], &:+)
12
13
  end
13
-
14
+
14
15
  extend MonadPlus
15
-
16
+
16
17
  def self.mzero
17
18
  []
18
19
  end
19
-
20
- alias_method :mplus, :+
20
+
21
+ def self.mplus(a, b)
22
+ a + b
23
+ end
21
24
  end
@@ -0,0 +1,16 @@
1
+ require 'do_notation'
2
+
3
+ module Identity
4
+ extend Monad
5
+
6
+ class << self
7
+ def unit(obj)
8
+ obj
9
+ end
10
+
11
+ def bind(obj, &fn)
12
+ fn.call(obj)
13
+ end
14
+ end
15
+ end
16
+
@@ -1,31 +1,32 @@
1
+ require 'do_notation'
1
2
  require 'do_notation/monad_plus'
2
3
 
3
- class Maybe < Struct.new(:value)
4
- include Monad
5
-
4
+ class Maybe
5
+ extend Monad
6
+
6
7
  def self.unit value
7
- self.new(value)
8
+ value
8
9
  end
9
-
10
- def bind &f
10
+
11
+ def self.bind value, &f
11
12
  if value.nil?
12
- self
13
+ nil
13
14
  else
14
15
  f.call(value)
15
16
  end
16
17
  end
17
-
18
+
18
19
  extend MonadPlus
19
-
20
+
20
21
  def self.mzero
21
22
  unit(nil)
22
23
  end
23
-
24
- def mplus m
25
- if value.nil?
26
- m
24
+
25
+ def self.mplus a, b
26
+ if b.nil?
27
+ a
27
28
  else
28
- self
29
+ b
29
30
  end
30
31
  end
31
32
  end
@@ -2,37 +2,39 @@
2
2
  # http://eigenclass.org/hiki/warm-fuzzy-things-for-random-simulations
3
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
4
 
5
- module PRNG
5
+ require 'do_notation'
6
+
7
+ module PRNG
6
8
  def next_state(s); (69069 * s + 5) % (2**32) end
7
9
  end
8
10
 
9
11
  class Simulation
10
12
  extend PRNG
11
- include Monad
12
-
13
+ extend Monad
14
+
13
15
  attr_reader :f
14
16
 
15
17
  def initialize(&b)
16
18
  @f = b
17
19
  end
18
-
20
+
19
21
  def self.unit(x)
20
22
  new { |s| [x, s] }
21
23
  end
22
-
24
+
23
25
  def self.rand(n)
24
26
  self.new do |s|
25
27
  [s.abs % n, next_state(s)]
26
28
  end
27
29
  end
28
30
 
29
- def bind(&b)
30
- self.class.new do |s|
31
- x, s = @f.call(s)
31
+ def self.bind(simulation, &b)
32
+ self.new do |s|
33
+ x, s = simulation.f.call(s)
32
34
  b.call(x).f.call(s)
33
35
  end
34
36
  end
35
-
37
+
36
38
  def play(s = 12345)
37
39
  @f.call(s).first
38
40
  end
@@ -40,33 +42,33 @@ end
40
42
 
41
43
  class Distribution
42
44
  extend PRNG
43
- include Monad
44
-
45
+ extend Monad
46
+
45
47
  attr_reader :a
46
48
 
47
49
  def initialize(a)
48
50
  @a = a
49
51
  end
50
-
52
+
51
53
  def self.wrap(a)
52
54
  new(a)
53
55
  end
54
-
56
+
55
57
  def self.unit(x)
56
58
  wrap [[x, 1.0]]
57
59
  end
58
60
 
59
- def self.rand(n)
61
+ def self.rand(n)
60
62
  p = 1.0 / n
61
63
  new((0...n).map{|i| [i, p]})
62
64
  end
63
65
 
64
- def bind(&b)
65
- if @a.empty?
66
- self.class.wrap([])
66
+ def self.bind(distribution, &b)
67
+ if distribution.a.empty?
68
+ wrap([])
67
69
  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
+ x, p = distribution.a[0]
71
+ wrap(mulp(p, b.call(x)) + bind(self.new(distribution.a[1..-1]), &b).a)
70
72
  end
71
73
  end
72
74
 
@@ -77,21 +79,21 @@ class Distribution
77
79
  end
78
80
 
79
81
  private
80
- def mulp(p, l)
82
+ def self.mulp(p, l)
81
83
  l.a.map{|x, p1| [x, p * p1]}
82
84
  end
83
85
  end
84
86
 
85
87
  class Expectation
86
88
  extend PRNG
87
- include Monad
88
-
89
+ extend Monad
90
+
89
91
  attr_reader :f
90
92
 
91
93
  def initialize(&b)
92
94
  @f = b
93
95
  end
94
-
96
+
95
97
  def self.wrap(&proc)
96
98
  new(&proc)
97
99
  end
@@ -107,8 +109,8 @@ class Expectation
107
109
  end
108
110
  end
109
111
 
110
- def bind(&b)
111
- self.class.wrap{|k| @f.call(lambda{|x| b.call(x).f.call(k)}) }
112
+ def self.bind(expectation, &b)
113
+ wrap{|k| expectation.f.call(lambda{|x| b.call(x).f.call(k)}) }
112
114
  end
113
115
 
114
116
  def play(x)
@@ -1,63 +1,45 @@
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
1
+ class DoNotation::Rewriter
2
+ def process exp
3
+ raise ArgumentError, "expected :iter, got #{exp[0].inspect}" unless exp[0] == :iter
4
+ raise ArgumentError, "expected s(:call, nil, :proc, s(:arglist)), got #{exp[1].inspect}" unless exp[1] == s(:call, nil, :proc, s(:arglist))
5
+ raise ArgumentError, "expected nil, got #{exp[2].inspect}" unless exp[2] == nil
6
+
7
+ if exp[3].is_a?(Sexp) and exp[3][0] == :block
8
+ iter, call, nil_val, block = exp.shift, exp.shift, exp.shift, exp.shift
9
+ raise ArgumentError, "unexpected extra syntax: #{exp.inspect}" unless exp.empty?
10
+ s(iter, call, nil_val, *rewrite_assignments(block[1..-1]))
11
11
  else
12
- body = s([*body])
12
+ exp
13
13
  end
14
-
15
- s(:iter, s(:fcall, :proc), nil, *rewrite_assignments(body))
16
14
  end
17
15
 
18
16
  def rewrite_assignments exp
19
17
  return [] if exp.empty?
20
-
18
+
21
19
  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]
20
+
21
+
22
+ if head[0] == :call and head[1] and head[1][0] == :call and head[2] == :< and head[3][0] == :arglist and head[3][1][2] == :-@
23
+ var_name = head[1][2]
25
24
  expression = head[3][1][1]
26
-
25
+
27
26
  body = rewrite_assignments(exp)
28
-
27
+
29
28
  if body.first.is_a? Symbol
30
29
  body = [s(*body)]
31
30
  end
32
-
31
+
33
32
  [s(:iter,
34
- s(:call, process(expression), :bind),
35
- s(:dasgn_curr, var_name),
33
+ s(:call, nil, :bind, s(:arglist, expression)),
34
+ s(:lasgn, var_name),
36
35
  *body)]
37
36
  elsif exp.empty?
38
- [process(head)]
37
+ [head]
39
38
  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}")
39
+ [s(:iter,
40
+ s(:call, nil, :bind_const, s(:arglist, head)),
41
+ nil,
42
+ *rewrite_assignments(exp))]
57
43
  end
58
-
59
- str << ')'
60
-
61
- str
62
44
  end
63
45
  end
@@ -6,8 +6,8 @@ describe "Array:" do
6
6
  array = Array.run do
7
7
  x <- ["first", "second"]
8
8
  y <- ["once", "twice"]
9
-
10
- unit("#{x} cousin #{y} removed")
9
+
10
+ ["#{x} cousin #{y} removed"]
11
11
  end
12
12
 
13
13
  array.should == ["first cousin once removed",
@@ -0,0 +1,16 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+ describe "DoNotation.pp" do
4
+ specify "should produce correct output" do
5
+ array = [:a, [:b],
6
+ [:c, :d,
7
+ :e]]
8
+
9
+ DoNotation.pp(array).should == <<CODE.strip
10
+ (:a (:b)
11
+ (:c :d
12
+ :e))
13
+ CODE
14
+ end
15
+ end
16
+
@@ -0,0 +1,27 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+ require 'do_notation/monads/identity'
3
+
4
+ describe "Identity:" do
5
+ specify "just works like sequential code" do
6
+ value = Identity.run do
7
+ x <- 1
8
+ y <- 2
9
+
10
+ x + y
11
+ end
12
+
13
+ value.should == 3
14
+ end
15
+
16
+ specify "doesn't do anything special with nil" do
17
+ lambda {
18
+ value = Identity.run do
19
+ x <- 1
20
+ y <- nil
21
+
22
+ x + y
23
+ end
24
+ }.should raise_error(TypeError)
25
+ end
26
+ end
27
+
@@ -3,24 +3,24 @@ require 'do_notation/monads/maybe'
3
3
 
4
4
  describe "Maybe:" do
5
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)
6
+ value = Maybe.run do
7
+ x <- 1
8
+ y <- nil
9
+
10
+ x + y
11
11
  end
12
12
 
13
- maybe.value.should == nil
13
+ value.should == nil
14
14
  end
15
15
 
16
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)
17
+ value = Maybe.run do
18
+ x <- 1
19
+ y <- 2
20
+
21
+ x + y
22
22
  end
23
-
24
- maybe.value.should == 3
23
+
24
+ value.should == 3
25
25
  end
26
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
+ [Maybe, Array].each do |monad|
7
+ describe monad do
8
+ specify "mzero >>= f = mzero" do
9
+ monad.bind(monad.mzero){ |x| unit(x+1) }.should == monad.mzero
10
+ end
11
+
12
+ specify "v >> mzero = mzero" do
13
+ monad.compose(monad.unit(1), monad.mzero).should == monad.mzero
14
+ end
15
+
16
+ specify "mzero `mplus` m = m" do
17
+ monad.mplus(monad.mzero, monad.unit(1)).should == monad.unit(1)
18
+ end
19
+
20
+ specify "m `mplus` mzero = m" do
21
+ monad.mplus(monad.unit(1), monad.mzero).should == monad.unit(1)
22
+ end
23
+ end
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
@@ -1,5 +1,7 @@
1
1
  require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
2
3
  require 'do_notation/monads/array'
4
+ require 'do_notation/monads/maybe'
3
5
 
4
6
  describe "Monad.run" do
5
7
  specify "should have lexical scope" do
@@ -10,7 +12,7 @@ describe "Monad.run" do
10
12
  x <- ["first", "second"]
11
13
  y <- ["once", "twice"]
12
14
 
13
- unit("#{x} #{foo} #{y} #{bar}")
15
+ ["#{x} #{foo} #{y} #{bar}"]
14
16
  end
15
17
 
16
18
  array.should == ["first cousin once removed",
@@ -18,26 +20,38 @@ describe "Monad.run" do
18
20
  "second cousin once removed",
19
21
  "second cousin twice removed"]
20
22
  end
21
-
23
+
24
+ it "can be run on the same block multiple times, using different monads" do
25
+ block = proc do
26
+ x <- unit(1)
27
+ y <- unit(2)
28
+ unit(x+y)
29
+ end
30
+
31
+ [Identity, Maybe, Array].each do |monad|
32
+ monad.run(&block).should == monad.unit(3)
33
+ end
34
+ end
35
+
22
36
  specify "should be nestable" do
23
37
  array = Array.run do
24
38
  x <- run do
25
39
  a <- ['A','a']
26
40
  b <- ['B','b']
27
41
 
28
- unit(a+b)
42
+ [a+b]
29
43
  end
30
44
 
31
45
  y <- run do
32
46
  c <- ['C','c']
33
47
  d <- ['D','d']
34
48
 
35
- unit(c+d)
49
+ [c+d]
36
50
  end
37
51
 
38
- unit(x+y)
52
+ [x+y]
39
53
  end
40
-
54
+
41
55
  array.should == ["ABCD", "ABCd", "ABcD", "ABcd",
42
56
  "AbCD", "AbCd", "AbcD", "Abcd",
43
57
  "aBCD", "aBCd", "aBcD", "aBcd",
@@ -45,22 +59,9 @@ describe "Monad.run" do
45
59
  end
46
60
  end
47
61
 
48
- describe 'Monad#>>' do
62
+ describe 'compose' do
49
63
  specify "should compose two values, discarding the first" do
50
- ([1] >> [2]).should == [2]
64
+ Array.compose([1], [2]).should == [2]
51
65
  end
52
66
  end
53
67
 
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
@@ -0,0 +1,68 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+ describe "Rewriter" do
4
+ it "leaves uninteresting blocks alone" do
5
+ in_block = proc { x + y }
6
+ out_block = proc { x + y }
7
+
8
+ process(in_block.to_sexp).should == out_block.to_sexp
9
+ end
10
+
11
+ it "rewrites the <- operator" do
12
+ in_block = proc do
13
+ x <- 1
14
+ y
15
+ end
16
+
17
+ out_block = proc do
18
+ bind 1 do |x|
19
+ y
20
+ end
21
+ end
22
+
23
+ process(in_block.to_sexp).should == out_block.to_sexp
24
+ end
25
+
26
+ it "rewrites multiple <- operators" do
27
+ in_block = proc do
28
+ x <- 1
29
+ y <- 2
30
+ z
31
+ end
32
+
33
+ out_block = proc do
34
+ bind 1 do |x|
35
+ bind 2 do |y|
36
+ z
37
+ end
38
+ end
39
+ end
40
+
41
+ process(in_block.to_sexp).should == out_block.to_sexp
42
+ end
43
+
44
+ it "rewrites statements without a <- to use bind_const" do
45
+ in_block = proc do
46
+ x <- 1
47
+ do_something
48
+ y <- 2
49
+ z
50
+ end
51
+
52
+ out_block = proc do
53
+ bind 1 do |x|
54
+ bind_const(do_something) do
55
+ bind 2 do |y|
56
+ z
57
+ end
58
+ end
59
+ end
60
+ end
61
+
62
+ process(in_block.to_sexp).should == out_block.to_sexp
63
+ end
64
+
65
+ def process(sexp)
66
+ DoNotation::Rewriter.new.process(sexp)
67
+ end
68
+ end
@@ -0,0 +1,65 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+ require 'do_notation/monads/simulations'
3
+
4
+ EPSILON = 0.001
5
+
6
+ RSpec::Matchers.define :approximate_floats do |expected|
7
+ match do |actual|
8
+ all_approximate?(actual, expected)
9
+ end
10
+
11
+ def all_approximate?(actual, expected)
12
+ case actual
13
+ when Array
14
+ actual.zip(expected).all? { |pair| all_approximate?(pair.first, pair.last) }
15
+ when Float
16
+ (expected - actual).abs < EPSILON
17
+ else
18
+ expected == actual
19
+ end
20
+ end
21
+ end
22
+
23
+ roll_3d6 = proc do
24
+ d1 <- rand(6)
25
+ d2 <- rand(6)
26
+ d3 <- rand(6)
27
+
28
+ unit(1+d1 + 1+d2 + 1+d3)
29
+ end
30
+
31
+ roll_3d6_b = proc do
32
+ d1 <- rand(6)
33
+ d2 <- rand(6)
34
+ d3 <- rand(6)
35
+
36
+ if [d1, d2, d3].include?(5)
37
+ run do
38
+ d4 <- rand(6)
39
+ unit(1+d1 + 1+d2 + 1+d3 + 1+d4)
40
+ end
41
+ else
42
+ unit(1+d1 + 1+d2 + 1+d3)
43
+ end
44
+ end
45
+
46
+ describe "Simulation" do
47
+ specify "generates correct answers" do
48
+ Simulation.run(&roll_3d6).play.should == 9
49
+ Simulation.run(&roll_3d6_b).play.should == 9
50
+ end
51
+ end
52
+
53
+ describe "Distribution" do
54
+ specify "generates correct answers" do
55
+ Distribution.run(&roll_3d6).play.should approximate_floats([[3, 0.005], [4, 0.014], [5, 0.028], [6, 0.046], [7, 0.069], [8, 0.097], [9, 0.116], [10, 0.125], [11, 0.125], [12, 0.116], [13, 0.097], [14, 0.069], [15, 0.046], [16, 0.028], [17, 0.014], [18, 0.005]])
56
+ Distribution.run(&roll_3d6_b).play.should approximate_floats([[3, 0.005], [4, 0.014], [5, 0.028], [6, 0.046], [7, 0.069], [8, 0.083], [9, 0.09], [10, 0.09], [11, 0.083], [12, 0.069], [13, 0.063], [14, 0.06], [15, 0.058], [16, 0.056], [17, 0.053], [18, 0.046], [19, 0.035], [20, 0.024], [21, 0.015], [22, 0.008], [23, 0.003], [24, 0.001]])
57
+ end
58
+ end
59
+
60
+ describe "Expectation" do
61
+ specify "generates correct answers" do
62
+ (1..21).collect { |x| Expectation.run(&roll_3d6).play(x) }.should approximate_floats([0.0, 0.0, 0.005, 0.014, 0.028, 0.046, 0.069, 0.097, 0.116, 0.125, 0.125, 0.116, 0.097, 0.069, 0.046, 0.028, 0.014, 0.005, 0.0, 0.0, 0.0])
63
+ (1..21).collect { |x| Expectation.run(&roll_3d6_b).play(x) }.should approximate_floats([0.0, 0.0, 0.005, 0.014, 0.028, 0.046, 0.069, 0.083, 0.09, 0.09, 0.083, 0.069, 0.063, 0.06, 0.058, 0.056, 0.053, 0.046, 0.035, 0.024, 0.015])
64
+ end
65
+ end
File without changes
metadata CHANGED
@@ -1,7 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: do_notation
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ hash: 19
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 3
9
+ - 0
10
+ version: 0.3.0
5
11
  platform: ruby
6
12
  authors:
7
13
  - Aanand Prasad
@@ -9,90 +15,116 @@ autorequire:
9
15
  bindir: bin
10
16
  cert_chain: []
11
17
 
12
- date: 2009-09-29 00:00:00 -04:00
13
- default_executable:
18
+ date: 2011-11-14 00:00:00 Z
14
19
  dependencies:
15
20
  - !ruby/object:Gem::Dependency
16
- name: ParseTree
21
+ name: sourcify
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ hash: 3
29
+ segments:
30
+ - 0
31
+ version: "0"
17
32
  type: :runtime
18
- version_requirement:
19
- version_requirements: !ruby/object:Gem::Requirement
33
+ version_requirements: *id001
34
+ - !ruby/object:Gem::Dependency
35
+ name: rake
36
+ prerelease: false
37
+ requirement: &id002 !ruby/object:Gem::Requirement
38
+ none: false
20
39
  requirements:
21
40
  - - ">="
22
41
  - !ruby/object:Gem::Version
42
+ hash: 3
43
+ segments:
44
+ - 0
23
45
  version: "0"
24
- version:
46
+ type: :development
47
+ version_requirements: *id002
25
48
  - !ruby/object:Gem::Dependency
26
- name: ruby2ruby
27
- type: :runtime
28
- version_requirement:
29
- version_requirements: !ruby/object:Gem::Requirement
49
+ name: rspec
50
+ prerelease: false
51
+ requirement: &id003 !ruby/object:Gem::Requirement
52
+ none: false
30
53
  requirements:
31
- - - "="
54
+ - - ~>
32
55
  - !ruby/object:Gem::Version
33
- version: 1.1.9
34
- version:
35
- description: Haskell-style monad do-notation for Ruby
56
+ hash: 3
57
+ segments:
58
+ - 2
59
+ - 0
60
+ version: "2.0"
61
+ type: :development
62
+ version_requirements: *id003
63
+ description:
36
64
  email: aanand.prasad@gmail.com
37
65
  executables: []
38
66
 
39
67
  extensions: []
40
68
 
41
- extra_rdoc_files:
42
- - README.rdoc
69
+ extra_rdoc_files: []
70
+
43
71
  files:
44
72
  - .gitignore
45
- - Manifest
46
- - README.rdoc
73
+ - .rspec
74
+ - .travis.yml
75
+ - Gemfile
76
+ - README.md
47
77
  - Rakefile
48
- - VERSION
49
- - do-notation.gemspec
78
+ - do_notation.gemspec
50
79
  - lib/do_notation.rb
51
80
  - lib/do_notation/monad.rb
52
81
  - lib/do_notation/monad_plus.rb
53
82
  - lib/do_notation/monads/array.rb
83
+ - lib/do_notation/monads/identity.rb
54
84
  - lib/do_notation/monads/maybe.rb
55
85
  - lib/do_notation/monads/simulations.rb
56
86
  - lib/do_notation/rewriter.rb
57
- - ruby-do-notation.gemspec
58
- - test/array.rb
59
- - test/maybe.rb
60
- - test/monad_plus.rb
61
- - test/simulations.rb
62
- - test/spec_helper.rb
63
- - test/specs.rb
64
- has_rdoc: true
65
- homepage: http://github.com/aanand/do_notation
87
+ - spec/array_spec.rb
88
+ - spec/do_notation_spec.rb
89
+ - spec/identity_spec.rb
90
+ - spec/maybe_spec.rb
91
+ - spec/monad_plus_spec.rb
92
+ - spec/monad_spec.rb
93
+ - spec/rewriter_spec.rb
94
+ - spec/simulations_spec.rb
95
+ - spec/spec_helper.rb
96
+ homepage: https://github.com/aanand/do_notation
66
97
  licenses: []
67
98
 
68
99
  post_install_message:
69
- rdoc_options:
70
- - --charset=UTF-8
100
+ rdoc_options: []
101
+
71
102
  require_paths:
72
103
  - lib
73
104
  required_ruby_version: !ruby/object:Gem::Requirement
105
+ none: false
74
106
  requirements:
75
107
  - - ">="
76
108
  - !ruby/object:Gem::Version
109
+ hash: 3
110
+ segments:
111
+ - 0
77
112
  version: "0"
78
- version:
79
113
  required_rubygems_version: !ruby/object:Gem::Requirement
114
+ none: false
80
115
  requirements:
81
116
  - - ">="
82
117
  - !ruby/object:Gem::Version
118
+ hash: 3
119
+ segments:
120
+ - 0
83
121
  version: "0"
84
- version:
85
122
  requirements: []
86
123
 
87
124
  rubyforge_project:
88
- rubygems_version: 1.3.4
125
+ rubygems_version: 1.8.10
89
126
  signing_key:
90
127
  specification_version: 3
91
128
  summary: Haskell-style monad do-notation for Ruby
92
- test_files:
93
- - test/array.rb
94
- - test/maybe.rb
95
- - test/monad_plus.rb
96
- - test/simulations.rb
97
- - test/spec_helper.rb
98
- - test/specs.rb
129
+ test_files: []
130
+
data/Manifest DELETED
@@ -1,15 +0,0 @@
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.rdoc
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
data/VERSION DELETED
@@ -1 +0,0 @@
1
- 0.2.0
data/do-notation.gemspec DELETED
@@ -1,67 +0,0 @@
1
- # Generated by jeweler
2
- # DO NOT EDIT THIS FILE
3
- # Instead, edit Jeweler::Tasks in Rakefile, and run `rake gemspec`
4
- # -*- encoding: utf-8 -*-
5
-
6
- Gem::Specification.new do |s|
7
- s.name = %q{do-notation}
8
- s.version = "0.2.0"
9
-
10
- s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
- s.authors = ["Aanand Prasad"]
12
- s.date = %q{2009-09-29}
13
- s.description = %q{Haskell-style monad do-notation for Ruby}
14
- s.email = %q{aanand.prasad@gmail.com}
15
- s.extra_rdoc_files = [
16
- "README.rdoc"
17
- ]
18
- s.files = [
19
- ".gitignore",
20
- "Manifest",
21
- "README.rdoc",
22
- "Rakefile",
23
- "lib/do_notation.rb",
24
- "lib/do_notation/monad.rb",
25
- "lib/do_notation/monad_plus.rb",
26
- "lib/do_notation/monads/array.rb",
27
- "lib/do_notation/monads/maybe.rb",
28
- "lib/do_notation/monads/simulations.rb",
29
- "lib/do_notation/rewriter.rb",
30
- "ruby-do-notation.gemspec",
31
- "test/array.rb",
32
- "test/maybe.rb",
33
- "test/monad_plus.rb",
34
- "test/simulations.rb",
35
- "test/spec_helper.rb",
36
- "test/specs.rb"
37
- ]
38
- s.homepage = %q{http://github.com/aanand/do-notation}
39
- s.rdoc_options = ["--charset=UTF-8"]
40
- s.require_paths = ["lib"]
41
- s.rubygems_version = %q{1.3.4}
42
- s.summary = %q{Haskell-style monad do-notation for Ruby}
43
- s.test_files = [
44
- "test/array.rb",
45
- "test/maybe.rb",
46
- "test/monad_plus.rb",
47
- "test/simulations.rb",
48
- "test/spec_helper.rb",
49
- "test/specs.rb"
50
- ]
51
-
52
- if s.respond_to? :specification_version then
53
- current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
54
- s.specification_version = 3
55
-
56
- if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
57
- s.add_runtime_dependency(%q<ParseTree>, [">= 0"])
58
- s.add_runtime_dependency(%q<ruby2ruby>, ["= 1.1.9"])
59
- else
60
- s.add_dependency(%q<ParseTree>, [">= 0"])
61
- s.add_dependency(%q<ruby2ruby>, ["= 1.1.9"])
62
- end
63
- else
64
- s.add_dependency(%q<ParseTree>, [">= 0"])
65
- s.add_dependency(%q<ruby2ruby>, ["= 1.1.9"])
66
- end
67
- end
@@ -1,59 +0,0 @@
1
-
2
- # Gem::Specification for Ruby-do-notation-0.2
3
- # Originally generated by Echoe
4
-
5
- Gem::Specification.new do |s|
6
- s.name = %q{ruby-do-notation}
7
- s.version = "0.2"
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.2'
58
- # p.dependencies = ['ParseTree', 'ruby2ruby']
59
- # end
data/test/monad_plus.rb DELETED
@@ -1,37 +0,0 @@
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
data/test/simulations.rb DELETED
@@ -1,46 +0,0 @@
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