deterministic 0.1.0 → 0.1.1

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.
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