deterministic 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 925b4c6bf8684358c92969bd99bfaae834ecc96d
4
- data.tar.gz: b9e69ef69dbd5fe6e050265d41a39375471b4acc
3
+ metadata.gz: 718affa7e1715d28293a98600c6112c800d32d3a
4
+ data.tar.gz: 600f1c5da38df13e625da735485af9e373a240e4
5
5
  SHA512:
6
- metadata.gz: e1d622aec6a69088627ab300883863815e85b7f67b4080072271644aaba31591942f2d23249751047f5d25e850a933414804a59641bb216a3e572a817aebde75
7
- data.tar.gz: 9637ca25bc179cfc53464b618e3c8e09332bd3e52085a1c750aaf2715feea65c5ccd701be4cf5388f334f5835007b532ff5f87d8b8edd142592e59bf3656f9a0
6
+ metadata.gz: fe73aa1b7315083c0ccfc507b240ef981293f2d8e3ceb89dc02ad3386ca5e14286b7fd944f911feb1e5d7ba9874fb364071e293062b77ef1172e9e43af334172
7
+ data.tar.gz: 6070733d16a169a9e2b4b7685b90805a5758fb5146ffc9803a26ce58793a24ce93f4711332ae320aac3a81bec51cd8b530ddcc3be93dbfd068bd29f10d13f33c
data/README.md CHANGED
@@ -1,20 +1,8 @@
1
1
  # Deterministic
2
2
 
3
- This is a spiritual successor of the [Monadic gem](http://github.com/pzol/monadic)
3
+ This is a spiritual successor of the [Monadic gem](http://github.com/pzol/monadic).
4
4
 
5
- ## Installation
6
-
7
- Add this line to your application's Gemfile:
8
-
9
- gem 'deterministic'
10
-
11
- And then execute:
12
-
13
- $ bundle
14
-
15
- Or install it yourself as:
16
-
17
- $ gem install deterministic
5
+ This gem is still __WORK IN PROGRESS__.
18
6
 
19
7
  ## Usage
20
8
 
@@ -80,11 +68,76 @@ Success(1).match do
80
68
  either { |v| "either #{v}"}
81
69
  end # => "either 1"
82
70
  ```
83
- Note: the inner value has been unwrapped!
71
+ Note1: the inner value has been unwrapped!
72
+
73
+ Note2: only the __last__ matching pattern block will be executed, so order __can__ be important.
74
+
75
+ The result returned will be the result of the __last__ `#try` or `#let`. As a side note, `#try` is a monad, `#let` is a functor.
76
+
77
+ Values for patterns are good, too:
78
+
79
+ ```ruby
80
+ Success(1).match do
81
+ success(1) { "Success #{v}" }
82
+ end # => "Success 1"
83
+ ```
84
+
85
+ You can and should also use procs for patterns:
86
+
87
+ ```ruby
88
+ Success(1).match do
89
+ success ->(v) { v == 1} { "Success #{v}" }
90
+ end # => "Success 1"
91
+ ```
92
+
93
+ Combining `#attempt_all` and `#match` is the ultimate sophistication:
84
94
 
85
- Note2: only the last matching pattern block will be executed
95
+ ```ruby
96
+ Either.attempt_all do
97
+ try { 1 }
98
+ try { |v| v + 1 }
99
+ end.match do
100
+ success(1) { |v| "We made it to step #{v}" }
101
+ success(2) { |v| "The correct answer is #{v}"}
102
+ end # => "The correct answer is 2"
103
+ ```
86
104
 
87
- The result returned will be the result of the last `#try` or `#let`
105
+ If no match was found a `NoMatchError` is raised, so make sure you always cover all possible outcomes.
106
+
107
+ ```ruby
108
+ Success(1).match do
109
+ failure(1) { "you'll never get me" }
110
+ end # => NoMatchError
111
+ ```
112
+
113
+ A way to have a catch-all would be using an `any`:
114
+
115
+ ```ruby
116
+ Success(1).match do
117
+ any { "catch-all" }
118
+ end # => "catch-all"
119
+ ```
120
+
121
+ ## Inspirations
122
+ * My [Monadic gem](http://github.com/pzol/monadic) of course
123
+ * `#attempt_all` was somewhat inspired by [An error monad in Clojure](http://brehaut.net/blog/2011/error_monads)
124
+ * [Pithyless' rumblings](https://gist.github.com/pithyless/2216519)
125
+ * [either by rsslldnphy](https://github.com/rsslldnphy/either)
126
+ * [Functors, Applicatives, And Monads In Pictures](http://adit.io/posts/2013-04-17-functors,_applicatives,_and_monads_in_pictures.html)
127
+
128
+ ## Installation
129
+
130
+ Add this line to your application's Gemfile:
131
+
132
+ gem 'deterministic'
133
+
134
+ And then execute:
135
+
136
+ $ bundle
137
+
138
+ Or install it yourself as:
139
+
140
+ $ gem install deterministic
88
141
 
89
142
  ## Contributing
90
143
 
@@ -93,8 +146,3 @@ The result returned will be the result of the last `#try` or `#let`
93
146
  3. Commit your changes (`git commit -am 'Add some feature'`)
94
147
  4. Push to the branch (`git push origin my-new-feature`)
95
148
  5. Create new Pull Request
96
-
97
- ## Inspirations
98
- * My [Monadic gem](http://github.com/pzol/monadic) of course
99
- * `#attempt_all` was somewhat inspired by [An error monad in Clojure](http://brehaut.net/blog/2011/error_monads)
100
- * [Pithyless' rumblings](https://gist.github.com/pithyless/2216519)
@@ -1,5 +1,6 @@
1
1
  require 'ostruct'
2
- module Deterministic::Either
2
+
3
+ class Deterministic::Either
3
4
  def self.attempt_all(context=OpenStruct.new, &block)
4
5
  AttemptAll.new(context, &block).call
5
6
  end
@@ -14,7 +15,7 @@ module Deterministic::Either
14
15
 
15
16
  def call(initial=nil)
16
17
  result = @tries.inject(Success(initial)) do |acc, try|
17
- acc.success? ? acc.bind(try.call(acc)) : acc
18
+ acc.success? ? acc << try.call(acc) : acc
18
19
  end
19
20
  end
20
21
 
@@ -1,3 +1,3 @@
1
- module Deterministic::Either
1
+ module Deterministic
2
2
  class Failure < Either; end
3
3
  end
@@ -1,44 +1,67 @@
1
- module Deterministic::Either
1
+ module Deterministic::PatternMatching
2
+
2
3
  def match(proc=nil, &block)
3
4
  match = Match.new(self)
4
5
  match.instance_eval &(proc || block)
5
6
  match.result
6
7
  end
7
8
 
9
+ class NoMatchError < StandardError; end
10
+
8
11
  class Match
9
- def initialize(either)
10
- @either = either
12
+ def initialize(container)
13
+ @container = container
11
14
  @collection = []
12
15
  end
13
16
 
14
- def success(value=nil, &block)
15
- q(:success, value, block)
17
+ def result
18
+ matcher = @collection.select { |m| m.matches?(@container.value) }.last
19
+ raise NoMatchError if matcher.nil?
20
+ matcher.result(@container.value)
16
21
  end
17
22
 
18
- def failure(value=nil, &block)
19
- q(:failure, value, block)
23
+ # TODO: Either specific DSL, will need to move it to Either, later on
24
+ %w[Success Failure Either].each do |s|
25
+ define_method s.downcase.to_sym do |value=nil, &block|
26
+ klas = Module.const_get(s)
27
+ push(klas, value, block)
28
+ end
20
29
  end
21
30
 
22
- def either(value=nil, &block)
23
- q(:either, value, block)
31
+ # catch-all
32
+ def any(value=nil, &result_block)
33
+ push(Object, value, result_block)
24
34
  end
25
35
 
26
- def result
27
- matcher = @collection.select { |m| m.first.call(@either.value) }.last
28
- matcher.last.call(@either.value)
36
+ private
37
+ Matcher = Struct.new(:condition, :block) do
38
+ def matches?(value)
39
+ condition.call(value)
40
+ end
41
+
42
+ def result(value)
43
+ block.call(value)
44
+ end
29
45
  end
30
46
 
31
- private
32
- def q(type, condition, block)
33
- if condition.nil?
34
- condition_p = ->(v) { true }
35
- elsif condition.is_a?(Proc)
36
- condition_p = condition
37
- else
38
- condition_p = ->(v) { condition == @either.value }
47
+ def push(type, condition, result_block)
48
+ condition_pred = case
49
+ when condition.nil?; ->(v) { true }
50
+ when condition.is_a?(Proc); condition
51
+ else ->(v) { condition == @container.value }
39
52
  end
40
53
 
41
- @collection << [condition_p, block] if @either.is? type
54
+ matcher_pred = compose_predicates(type_pred[type], condition_pred)
55
+ @collection << Matcher.new(matcher_pred, result_block)
56
+ end
57
+
58
+ def compose_predicates(f, g)
59
+ ->(*args) { f[*args] && g[*args] }
60
+ end
61
+
62
+ # return a partial function for matching a matcher's type
63
+ def type_pred
64
+ (->(type, x) { @container.is_a? type }).curry
42
65
  end
43
66
  end
44
67
  end
@@ -1,3 +1,3 @@
1
- module Deterministic::Either
1
+ module Deterministic
2
2
  class Success < Either; end
3
3
  end
@@ -1,24 +1,15 @@
1
- module Deterministic::Either
1
+ module Deterministic
2
2
  def Success(value)
3
- Success.unit(value)
3
+ Success.new(value)
4
4
  end
5
5
 
6
6
  def Failure(value)
7
- Failure.unit(value)
7
+ Failure.new(value)
8
8
  end
9
9
 
10
10
  class Either
11
- def self.unit(value)
12
- return value if value.is_a? Either
13
- # return Failure.new(value) if value.nil? || (value.respond_to?(:empty?) && value.empty?) || !value
14
- # return Success.new(value)
15
- return new(value)
16
- end
17
-
18
- def is?(s)
19
- const_name = s.slice(0,1).capitalize + s.slice(1..-1)
20
- is_a? Module.const_get(const_name)
21
- end
11
+ include Monad
12
+ include Deterministic::PatternMatching
22
13
 
23
14
  def success?
24
15
  is_a? Success
@@ -28,31 +19,9 @@ module Deterministic::Either
28
19
  is_a? Failure
29
20
  end
30
21
 
31
- def bind(other)
22
+ def <<(other)
32
23
  return self if failure?
33
24
  return other if other.is_a? Either
34
- # return concat(proc) if proc.is_a? Either
35
-
36
- # begin
37
- # Either(call(proc, block))
38
- # rescue StandardError => error
39
- # Failure(error)
40
- # end
41
- end
42
-
43
- # get the underlying value
44
- def value
45
- @value
46
- end
47
-
48
- def ==(other)
49
- return false unless other.is_a? self.class
50
- @value == other.instance_variable_get(:@value)
51
- end
52
-
53
- private
54
- def initialize(value)
55
- @value = value
56
25
  end
57
26
  end
58
27
  end
@@ -0,0 +1,49 @@
1
+ module Deterministic
2
+ module Monad
3
+ class NotMonadError < StandardError; end
4
+
5
+ def initialize(value)
6
+ @value = join(value)
7
+ end
8
+
9
+ # If the passed value is monad already, get the value to avoid nesting
10
+ # M[M[A]] is equivalent to M[A]
11
+ def join(value)
12
+ if value.is_a? self.class then value.value
13
+ else value end
14
+ end
15
+
16
+ # The functor: takes a function (a -> b) and applies it to the inner value of the monad (Ma),
17
+ # boxes it back to the same monad (Mb)
18
+ # fmap :: (a -> b) -> Ma -> Mb
19
+ def map(proc=nil, &block)
20
+ result = (proc || block).call(value)
21
+ self.class.new(result)
22
+ end
23
+
24
+ # The monad: takes a function which returns a monad, applies
25
+ # bind :: Ma -> (a -> Mb) -> Mb
26
+ def bind(proc=nil, &block)
27
+ result = (proc || block).call(value)
28
+ raise NotMonadError unless result.is_a? Monad
29
+ self.class.new(result)
30
+ end
31
+
32
+ # Get the underlying value, return in Haskell
33
+ # return :: m a -> a
34
+ def value
35
+ @value
36
+ end
37
+
38
+ def ==(other)
39
+ return false unless other.is_a? self.class
40
+ @value == other.instance_variable_get(:@value)
41
+ end
42
+
43
+ # Return the string representation of the Monad
44
+ def to_s
45
+ pretty_class_name = self.class.name.split('::')[-1]
46
+ "#{pretty_class_name}(#{self.value.inspect})"
47
+ end
48
+ end
49
+ end
@@ -1,3 +1,3 @@
1
1
  module Deterministic
2
- VERSION = "0.1.0"
2
+ VERSION = "0.1.1"
3
3
  end
data/lib/deterministic.rb CHANGED
@@ -2,8 +2,9 @@ require "deterministic/version"
2
2
 
3
3
  module Deterministic; end
4
4
 
5
- require 'deterministic/either'
5
+ require 'deterministic/monad'
6
6
  require 'deterministic/either/match'
7
+ require 'deterministic/either'
7
8
  require 'deterministic/either/attempt_all'
8
9
  require 'deterministic/either/success'
9
10
  require 'deterministic/either/failure'
@@ -1,7 +1,6 @@
1
1
  require 'spec_helper'
2
2
  require 'deterministic'
3
3
 
4
- include Deterministic::Either
5
4
  include Deterministic
6
5
 
7
6
  describe Deterministic::Either::AttemptAll do
@@ -1,7 +1,7 @@
1
1
  require 'spec_helper'
2
2
  require 'deterministic'
3
3
 
4
- include Deterministic::Either
4
+ include Deterministic
5
5
 
6
6
  describe Deterministic::Either::Match do
7
7
  it "can match Success" do
@@ -34,6 +34,14 @@ describe Deterministic::Either::Match do
34
34
  ).to eq "matched 2"
35
35
  end
36
36
 
37
+ it "catch-all" do
38
+ expect(
39
+ Success(1).match do
40
+ any { "catch-all" }
41
+ end
42
+ ).to eq "catch-all"
43
+ end
44
+
37
45
  it "can match either" do
38
46
  expect(
39
47
  Failure(2).match do
@@ -44,11 +52,19 @@ describe Deterministic::Either::Match do
44
52
  ).to eq "either 2"
45
53
  end
46
54
 
47
- it "can mach with lambdas" do
55
+ it "can match with lambdas" do
48
56
  expect(
49
57
  Success(1).match do
50
58
  success ->(v) { v == 1 } { |v| "matched #{v}" }
51
59
  end
52
60
  ).to eq "matched 1"
53
61
  end
62
+
63
+ it "no match" do
64
+ expect {
65
+ Success(1).match do
66
+ failure { "you'll never get me" }
67
+ end
68
+ }.to raise_error Deterministic::PatternMatching::NoMatchError
69
+ end
54
70
  end
@@ -1,10 +1,16 @@
1
1
  require 'spec_helper'
2
+ require_relative '../monad_axioms'
2
3
  require 'deterministic'
3
4
 
4
- include Deterministic::Either
5
+ include Deterministic
5
6
 
6
- describe Deterministic::Either::Success do
7
- subject { described_class.unit(1) }
7
+ describe Deterministic::Success do
8
+
9
+ it_behaves_like 'a Monad' do
10
+ let(:monad) { described_class }
11
+ end
12
+
13
+ subject { described_class.new(1) }
8
14
 
9
15
  specify { expect(subject).to be_an_instance_of described_class }
10
16
  specify { expect(subject.value).to eq 1 }
@@ -12,16 +18,18 @@ describe Deterministic::Either::Success do
12
18
  specify { expect(subject).not_to be_failure }
13
19
 
14
20
  # public constructor #Success[]
15
- specify { expect(described_class.unit(1)).to be_an_instance_of described_class }
16
- specify { expect(subject).to eq(described_class.unit(1))}
21
+ specify { expect(subject).to be_an_instance_of described_class }
22
+ specify { expect(subject).to eq(described_class.new(1)) }
23
+ specify { expect(subject << Success(2)).to eq(Success(2)) }
24
+ specify { expect(subject << Failure(2)).to eq(Failure(2)) }
25
+ specify { expect(Success(subject)).to eq Success(1) }
26
+ specify { expect(subject.map { |v| v + 1} ).to eq Success(2) }
17
27
 
18
- it "handles chaining using &" do
19
- expect(Success(1).bind Success(2)).to eq(Success(2))
20
- expect(Success(1).bind Failure(2)).to eq(Failure(2))
28
+ it "#bind" do
29
+ expect(
30
+ subject.bind { |v| true ? Success(v + 1) : Failure(v + 2)}
31
+ ).to eq Success(2)
21
32
  end
22
33
 
23
- specify { expect(Success(Success(1))).to eq Success(1) }
24
- specify { expect(Success(1).is? :success).to be true }
25
- specify { expect(Success(1).is? :either).to be true }
26
- specify { expect(Success(1).is? :failure).to be false }
34
+ specify { expect { Success("a").bind(&:upcase) }.to raise_error(Deterministic::Monad::NotMonadError) }
27
35
  end
@@ -0,0 +1,48 @@
1
+ shared_examples 'a Monad' do
2
+ describe 'axioms' do
3
+ it '1st monadic law: left-identity' do
4
+ f = ->(value) { monad.new(value + 1) }
5
+ expect(
6
+ monad::new(1).bind do |value|
7
+ f.(value)
8
+ end
9
+ ).to eq f.(1)
10
+ end
11
+
12
+ it '2nd monadic law: right-identy - new and bind do not change the value' do
13
+ expect(
14
+ monad.new(1).bind do |value|
15
+ monad.new(value)
16
+ end
17
+ ).to eq monad.new(1)
18
+ end
19
+
20
+ it '3rd monadic law: associativity' do
21
+ f = ->(value) { monad.new(value + 1) }
22
+ g = ->(value) { monad.new(value + 100) }
23
+
24
+ id1 = monad.new(1).bind do |a|
25
+ f.(a)
26
+ end.bind do |b|
27
+ g.(b)
28
+ end
29
+
30
+ id2 = monad.new(1).bind do |a|
31
+ f.(a).bind do |b|
32
+ g.(b)
33
+ end
34
+ end
35
+
36
+ expect(id1).to eq id2
37
+ end
38
+
39
+ it '#bind must return a monad' do
40
+ expect(monad.new(1).bind { |v| monad.new(v) }).to eq monad.new(1)
41
+ expect { monad.new(1).bind {} }.to raise_error(Deterministic::Monad::NotMonadError)
42
+ end
43
+
44
+ it '#new must return a monad' do
45
+ expect(monad.new(1)).to be_a monad
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,29 @@
1
+ require 'spec_helper'
2
+ require_relative 'monad_axioms'
3
+ require 'deterministic'
4
+
5
+
6
+ describe Deterministic::Monad do
7
+ class Identity
8
+ include Deterministic::Monad
9
+ end
10
+
11
+ it_behaves_like 'a Monad' do
12
+ let(:monad) { Identity }
13
+ end
14
+
15
+ specify { expect(Identity.new(1).to_s).to eq 'Identity(1)' }
16
+ specify { expect(Identity.new(nil).to_s).to eq 'Identity(nil)' }
17
+ specify { expect(Identity.new([1, 2]).map(&:to_s)).to eq Identity.new("[1, 2]") }
18
+ specify { expect(Identity.new(1).map {|v| v + 2}).to eq Identity.new(3) }
19
+ specify { expect(Identity.new('foo').map(&:upcase)).to eq Identity.new('FOO')}
20
+ specify { expect { Identity.new(1).bind {} }.to raise_error(Deterministic::Monad::NotMonadError) }
21
+ specify { expect(Identity.new(Identity.new(1))).to eq Identity.new(1) }
22
+
23
+ # it 'delegates #flat_map to an underlying collection and wraps the resulting collection' do
24
+ # Identity.unit([1,2]).flat_map {|v| v + 1}.should == Identity.unit([2, 3])
25
+ # Identity.unit(['foo', 'bar']).flat_map(&:upcase).should == Identity.unit(['FOO', 'BAR'])
26
+ # expect { Identity.unit(1).flat_map {|v| v + 1 } }.to raise_error(RuntimeError)
27
+ # end
28
+
29
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: deterministic
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Piotr Zolnierek
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-12-27 00:00:00.000000000 Z
11
+ date: 2013-12-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -130,10 +130,13 @@ files:
130
130
  - lib/deterministic/either/failure.rb
131
131
  - lib/deterministic/either/match.rb
132
132
  - lib/deterministic/either/success.rb
133
+ - lib/deterministic/monad.rb
133
134
  - lib/deterministic/version.rb
134
135
  - spec/lib/deterministic/attempt_all_spec.rb
135
136
  - spec/lib/deterministic/either/match_spec.rb
136
137
  - spec/lib/deterministic/either/success_spec.rb
138
+ - spec/lib/deterministic/monad_axioms.rb
139
+ - spec/lib/deterministic/monad_spec.rb
137
140
  - spec/spec_helper.rb
138
141
  homepage: http://github.com/pzol/deterministic
139
142
  licenses:
@@ -163,4 +166,6 @@ test_files:
163
166
  - spec/lib/deterministic/attempt_all_spec.rb
164
167
  - spec/lib/deterministic/either/match_spec.rb
165
168
  - spec/lib/deterministic/either/success_spec.rb
169
+ - spec/lib/deterministic/monad_axioms.rb
170
+ - spec/lib/deterministic/monad_spec.rb
166
171
  - spec/spec_helper.rb