deterministic 0.14.1 → 0.15.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.
@@ -3,15 +3,15 @@ module Deterministic
3
3
  class NotMonadError < StandardError; end
4
4
 
5
5
  # Basicly the `pure` function
6
- def initialize(value)
7
- @value = join(value)
6
+ def initialize(init)
7
+ @value = join(init)
8
8
  end
9
9
 
10
10
  # If the passed value is monad already, get the value to avoid nesting
11
11
  # M[M[A]] is equivalent to M[A]
12
- def join(value)
13
- if value.is_a? self.class then value.value
14
- else value end
12
+ def join(other)
13
+ if other.is_a? self.class then other.value
14
+ else other end
15
15
  end
16
16
 
17
17
  # The functor: takes a function (a -> b) and applies it to the inner value of the monad (Ma),
@@ -27,6 +27,20 @@ module Deterministic
27
27
  # the self.class, i.e. the containing monad is passed as a second (optional) arg to the function
28
28
  def bind(proc=nil, &block)
29
29
  (proc || block).call(value).tap do |result|
30
+
31
+ # def parent_name(obj)
32
+ # parts = obj.class.name.split('::')
33
+ # if parts.count > 1
34
+ # parts[0..-2].join('::')
35
+ # else
36
+ # parts[0]
37
+ # end
38
+ # end
39
+
40
+ # self_parent = parent_name(self)
41
+ # other_parent = parent_name(result)
42
+ # raise NotMonadError, "Expected #{result.inspect} to be an #{other_parent}" unless self_parent == other_parent
43
+
30
44
  parent = self.class.superclass === Object ? self.class : self.class.superclass
31
45
  raise NotMonadError, "Expected #{result.inspect} to be an #{parent}" unless result.is_a? parent
32
46
  end
@@ -1,28 +1,11 @@
1
1
  module Deterministic
2
- # Abstract parent of Some and None
3
- class Option
4
- include Monad
5
-
6
- module PatternMatching
7
- include Deterministic::PatternMatching
8
- class Match
9
- include Deterministic::PatternMatching::Match
10
-
11
- %w[Some None Option].each do |s|
12
- define_method s.downcase.to_sym do |value=nil, &block|
13
- klas = self.class.module_eval(s)
14
- push(klas, value, block)
15
- end
16
- end
17
- end
18
- end
19
-
20
- include PatternMatching
2
+ Option = Deterministic::enum {
3
+ Some(:s)
4
+ None()
5
+ }
21
6
 
22
- # This is an abstract class, can't ever instantiate it directly
7
+ class Option
23
8
  class << self
24
- protected :new
25
-
26
9
  def some?(expr)
27
10
  to_option(expr) { expr.nil? }
28
11
  end
@@ -39,82 +22,59 @@ module Deterministic
39
22
  yield rescue None.new
40
23
  end
41
24
  end
25
+ end
42
26
 
43
- def map(proc=nil, &block)
44
- return self if none?
45
- bind(proc || block)
46
- end
27
+ impl(Option) {
28
+ class NoneValueError < StandardError; end
47
29
 
48
- ## Convert the inner value to an Array
49
- def value_to_a
50
- map { self.class.new(Array(value)) }
30
+ def fmap(&fn)
31
+ match {
32
+ Some(s) { |m| m.class.new(fn.(s)) }
33
+ None() { |n| n }
34
+ }
51
35
  end
52
36
 
53
- ## Add the inner values of two Some
54
- def +(other)
55
- return other if none?
56
- fmap { |v|
57
- other.match {
58
- some { v + other.value }
59
- none { self }
60
- }
37
+ def map(&fn)
38
+ match {
39
+ Some(s) { |m| m.bind(&fn) }
40
+ None() { |n| n }
61
41
  }
62
42
  end
63
43
 
64
44
  def some?
65
- is_a? Some
45
+ is_a? Option::Some
66
46
  end
67
47
 
68
48
  def none?
69
- is_a? None
49
+ is_a? Option::None
70
50
  end
71
51
 
72
- def value_or(default)
73
- return default if none?
74
- return value
52
+ def value_or(n)
53
+ match {
54
+ Some(s) { s }
55
+ None() { n }
56
+ }
75
57
  end
76
58
 
77
- class Some < Option
78
- class << self; public :new; end
59
+ def value_to_a
60
+ @value
79
61
  end
80
62
 
81
- class None < Option
82
- class << self
83
- public :new
84
-
85
- def instance
86
- @instance ||= new
87
- end
88
- end
89
-
90
- def initialize(*args)
91
- @value = self
92
- end
93
-
94
- def inspect
95
- "None"
96
- end
97
-
98
- private :value
99
-
100
- def fmap(*args)
101
- self
102
- end
103
-
104
- alias :map :fmap
105
-
106
- def ==(other)
107
- other.class == self.class
108
- end
63
+ def +(other)
64
+ match {
65
+ None() { other }
66
+ Some(_, where { !other.is_a?(Option)}) { raise TypeError, "Other must be an #{Option}"}
67
+ Some(s, where { other.some? }) { Option::Some.new(s + other.value) }
68
+ Some(_) { |s| s }
69
+ }
109
70
  end
110
- end
71
+ }
111
72
 
112
- module_function
113
- def Some(value)
114
- Option::Some.new(value)
73
+ module Prelude
74
+ module Option
75
+ None = Deterministic::Option::None.new
76
+ def Some(s); Deterministic::Option::Some.new(s); end
77
+ def None(); Deterministic::Prelude::Option::None; end
78
+ end
115
79
  end
116
-
117
- Some = Deterministic::Option::Some
118
- None = Deterministic::Option::None.instance
119
80
  end
120
- # p Deterministic::Option::Some::Match.new(.methods
@@ -0,0 +1,103 @@
1
+ module Deterministic
2
+ class ProtocolBuilder
3
+
4
+ def initialize(typevar, block)
5
+ @typevar, @block = typevar, block
6
+ @protocol = Class.new
7
+ end
8
+
9
+ def build
10
+ instance_exec(&@block)
11
+ @protocol
12
+ end
13
+
14
+ def method_missing(m, *args)
15
+ [m, args]
16
+ end
17
+
18
+ Signature = Struct.new(:name, :params, :return_type, :block)
19
+
20
+ def fn(signature, &block)
21
+ m = signature.to_a.flatten
22
+ name = m[0]
23
+ return_type = m[-1]
24
+ params = Hash[(m[1..-2][0] || {}).map { |k, v| [k[0], v] }]
25
+
26
+ @protocol.instance_eval {
27
+ define_singleton_method(name) {
28
+ Signature.new(name, params, return_type, block)
29
+ }
30
+ }
31
+
32
+ @protocol.instance_eval {
33
+ if block
34
+ define_method(name) { |*args|
35
+ block.call(args)
36
+ }
37
+ end
38
+ }
39
+ end
40
+ end
41
+
42
+ class InstanceBuilder
43
+ def initialize(protocol, type, block)
44
+ @protocol, @type, @block = protocol, type, block
45
+ @instance = Class.new(@protocol::Protocol)
46
+ end
47
+
48
+ def build
49
+ @instance.class_exec(&@block)
50
+ protocol = @protocol::Protocol
51
+ methods = protocol.methods(false)
52
+ inst_type = @type
53
+
54
+ @instance.instance_exec {
55
+ methods.each { |name|
56
+ if method_defined?(name)
57
+ meth = instance_method(name)
58
+ signature = protocol.send(name)
59
+ params = signature.params
60
+ expect_type = inst_type[signature.return_type]
61
+
62
+ define_method(name) { |*args|
63
+ args.each_with_index { |arg, i|
64
+ name = params.keys[i]
65
+ arg_type = params.fetch(name)
66
+ expect_arg_type = inst_type.fetch(arg_type)
67
+
68
+ raise TypeError, "Expected arg #{name} to be a #{expect_arg_type}, got #<#{arg.class}: #{arg.inspect}>" unless arg.is_a? expect_arg_type
69
+ }
70
+
71
+ result = meth.bind(self).call(*args)
72
+ raise TypeError, "Expected #{name}(#{args.join(', ')}) to return a #{expect_type}, got #<#{result.class}: #{result.inspect}>" unless result.is_a? expect_type
73
+ result
74
+ }
75
+ end
76
+ }
77
+ }
78
+
79
+ missing = methods.detect { |m| !@instance.instance_methods(false).include?(m) }
80
+
81
+ raise NotImplementedError, "`#{missing}` has no default implementation for #{@protocol} #{@type.to_s}" unless missing.nil?
82
+
83
+ @instance
84
+ end
85
+ end
86
+
87
+ module_function
88
+ def protocol(typevar, &block)
89
+ protocol = ProtocolBuilder.new(typevar, block).build
90
+ p_module = block.binding.eval('self')
91
+ p_module.const_set(:Protocol, protocol)
92
+ end
93
+
94
+ def instance(protocol, type, &block)
95
+ InstanceBuilder.new(protocol, type, block).build
96
+ end
97
+
98
+ module Protocol
99
+ def const_missing(c)
100
+ c
101
+ end
102
+ end
103
+ end
@@ -1,122 +1,80 @@
1
- module Deterministic
2
- # Abstract parent of Success and Failure
3
- class Result
4
- include Monad
5
1
 
6
- module PatternMatching
7
- include Deterministic::PatternMatching
8
- class Match
9
- include Deterministic::PatternMatching::Match
2
+ module Deterministic
3
+ Result = Deterministic::enum {
4
+ Success(:s)
5
+ Failure(:f)
6
+ }
10
7
 
11
- %w[Success Failure Result].each do |s|
12
- define_method s.downcase.to_sym do |value=nil, &block|
13
- klas = klas = self.class.module_eval(s)
14
- push(klas, value, block)
15
- end
16
- end
17
- end
8
+ Deterministic::impl(Result) {
9
+ def map(proc=nil, &block)
10
+ match {
11
+ Success(_) { |s| s.bind(proc || block) }
12
+ Failure(_) { |f| f }
13
+ }
18
14
  end
19
15
 
20
- include PatternMatching
21
-
22
- # This is an abstract class, can't ever instantiate it directly
23
- class << self
24
- protected :new
25
- end
16
+ alias :>> :map
17
+ alias :and_then :map
26
18
 
27
- def success?
28
- is_a? Success
19
+ def map_err(proc=nil, &block)
20
+ match {
21
+ Success(_) { |s| s }
22
+ Failure(_) { |f| f.bind(proc|| block) }
23
+ }
29
24
  end
30
25
 
31
- def failure?
32
- is_a? Failure
33
- end
26
+ alias :or_else :map_err
34
27
 
35
- # `pipe(self: Result(a), op: |Result(a)| -> b) -> Result(a)`
36
- # Executes the block passed, but completely ignores its result. If an error is raised within the block it will **NOT** be catched.
37
28
  def pipe(proc=nil, &block)
38
29
  (proc || block).call(self)
39
30
  self
40
31
  end
41
32
 
42
- alias :** :pipe
43
- alias :<< :pipe
33
+ alias :<< :pipe
44
34
 
45
- # `pipe(self: Result(a), op: |Result(a)| -> b) -> Result(a)`
46
- # Replaces `Success a` with `Result b`. If a `Failure` is passed as argument, it is ignored.
47
- def and(other)
48
- return self if failure?
49
- raise NotMonadError, "Expected #{other.inspect} to be an Result" unless other.is_a? Result
50
- other
51
- end
52
-
53
- # `or(self: Failure(a), other: Result(b)) -> Result(b)`
54
- # Replaces `Failure a` with `Result`. If a `Failure` is passed as argument, it is ignored.
55
- def or(other)
56
- return self if success?
57
- raise NotMonadError, "Expected #{other.inspect} to be an Result" unless other.is_a? Result
58
- return other
35
+ def success?
36
+ is_a? Result::Success
59
37
  end
60
38
 
61
- # `map(self: Success(a), op: |a| -> Result(b)) -> Result(b)`
62
- # Maps a `Success` with the value `a` to another `Result` with the value `b`. It works like `#bind` but only on `Success`.
63
- def map(proc=nil, &block)
64
- return self if failure?
65
- bind(proc || block)
39
+ def failure?
40
+ is_a? Result::Failure
66
41
  end
67
42
 
68
- alias :>> :map
69
- alias :and_then :map
70
-
71
- # `map_err(self: Failure(a), op: |a| -> Result(b)) -> Result(b)`
72
- # Maps a `Failure` with the value `a` to another `Result` with the value `b`. It works like `#bind` but only on `Failure`.
73
- def map_err(proc=nil, &block)
74
- return self if success?
75
- bind(proc || block)
43
+ def or(other)
44
+ raise Deterministic::Monad::NotMonadError, "Expected #{other.inspect} to be a Result" unless other.is_a? Result
45
+ match {
46
+ Success(_) { |s| s }
47
+ Failure(_) { other}
48
+ }
76
49
  end
77
50
 
78
- alias :or_else :map_err
79
-
80
- # `pipe(self: Result(a), op: |Result(a)| -> b) -> Result(a)`
81
- # Executes the block passed, but completely ignores its result. If an error is raised within the block it will **NOT** be catched.
82
- def try(proc=nil, &block)
83
- map(proc, &block)
84
- rescue => err
85
- Result::Failure.new(err)
51
+ def and(other)
52
+ raise Deterministic::Monad::NotMonadError, "Expected #{other.inspect} to be a Result" unless other.is_a? Result
53
+ match {
54
+ Success(_) { other }
55
+ Failure(_) { |f| f }
56
+ }
86
57
  end
87
58
 
88
- # `+(self: Result(a), other: Result(a)) -> Result(a -> a)`
89
- # Add the inner values of two Result
90
59
  def +(other)
91
- raise Deterministic::Monad::NotMonadError, "Expected #{other.inspect} to be an Result" unless other.is_a? Result
60
+ raise Deterministic::Monad::NotMonadError, "Expected #{other.inspect} to be a Result" unless other.is_a? Result
92
61
  match {
93
- success ->(_) { other.success? } { |s| Result::Success.new(s + other.value)}
94
- failure ->(_) { other.failure? } { |f| Result::Failure.new(f + other.value)}
95
- success { other } # implied other.failure?
96
- failure { self } # implied other.success?
62
+ Success(s, where { other.success?} ) { Result::Success.new(s + other.value) }
63
+ Failure(f, where { other.failure?} ) { Result::Failure.new(f + other.value) }
64
+ Success(_) { other } # implied other.failure?
65
+ Failure(_) { |f| f } # implied other.success?
97
66
  }
98
67
  end
68
+ }
69
+ end
99
70
 
100
- alias :>= :try
101
-
102
- class Failure < Result
103
- class << self; public :new; end
104
- end
105
-
106
- class Success < Result
107
- class << self; public :new; end
71
+ module Deterministic
72
+ module Prelude
73
+ module Result
74
+ def Success(s); Deterministic::Result::Success.new(s); end
75
+ def Failure(f); Deterministic::Result::Failure.new(f); end
108
76
  end
109
- end
110
77
 
111
- module_function
112
- def Success(value)
113
- Result::Success.new(value)
78
+ include Result
114
79
  end
115
-
116
- def Failure(value)
117
- Result::Failure.new(value)
118
- end
119
-
120
- Success = Result::Success
121
- Failure = Result::Failure
122
80
  end