mon 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/lib/contracts/contract_helpers.rb +12 -0
- data/lib/contracts/future.rb +28 -0
- data/lib/contracts/lazy.rb +27 -0
- data/lib/contracts/list.rb +22 -0
- data/lib/contracts/maybe.rb +15 -0
- data/lib/contracts/monad_contract.rb +57 -0
- data/lib/contracts/reactron.rb +23 -0
- data/lib/contracts/try.rb +23 -0
- data/lib/mon.rb +21 -0
- data/lib/monads/chainable_monad.rb +23 -0
- data/lib/monads/future.rb +154 -0
- data/lib/monads/lazy.rb +200 -0
- data/lib/monads/list.rb +89 -0
- data/lib/monads/maybe.rb +211 -0
- data/lib/monads/monad.rb +11 -0
- data/lib/monads/reactron.rb +175 -0
- data/lib/monads/try.rb +190 -0
- data/spec/contract_spec.rb +310 -0
- data/spec/monad_spec.rb +757 -0
- metadata +77 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: a7a736fe71441c3d09d26400d69898a67e4188ca
|
4
|
+
data.tar.gz: d68172fc1348c0d680e9f04f9dd4eeb4369f4f83
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: bc9996b5de285ab1f70da5cf4b5ea8ccdb57f917ae1318d02135a050798662b7360fe0abdec5b640e4f4de0b9756d9a08b4fa9c3a72843fb0792d2ed07f45ead
|
7
|
+
data.tar.gz: 647fb1982589a44e407a2d8c4e890d28c8d12ac3eafc7b5ee22acf09e6096ebcd6081ce2ef4a3accebd3af1c698b5b0bc0281f825cadd053490b287c1f5e3451
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# Contract for the Mon::Future monad, for use with the Contracts gem
|
2
|
+
# For example:
|
3
|
+
# <tt>Contract C::Future[Num] => C::Future[Num]
|
4
|
+
# def futureSquare(l)
|
5
|
+
# l.bind { |i| i * i }
|
6
|
+
# end</tt>
|
7
|
+
# will work only for (eg) futureSquare(Mon::Future.eventually { wait_for_number_from_thread }).
|
8
|
+
# If passed anything (or returning anything) other than a Mon::Future, a
|
9
|
+
# ContractViolation will be thrown. <b>Note:</b> since Ruby is dynamically typed,
|
10
|
+
# we can't ensure that the eventual value will be of the correct type. So:
|
11
|
+
# futureSquare(Mon::Future.eventually { wait_for_string_from_thread }) is likely
|
12
|
+
# to succeed (if the thread is still in-flight), and fail when the thread returns.
|
13
|
+
# Completed Futures don't have this issue.
|
14
|
+
|
15
|
+
module Mon
|
16
|
+
module Contract
|
17
|
+
require_relative 'monad_contract'
|
18
|
+
|
19
|
+
class Future < MonadContract
|
20
|
+
def valid?(val)
|
21
|
+
# Should be a Future, and if finalized it should satisfy
|
22
|
+
# the provided contract. Note: We can't know if an inflight
|
23
|
+
# contract will satisfy type or not.
|
24
|
+
val.is_a?(Mon::M::Future) and (val.is_a?(Mon::M::FutureComplete).implies(valid_nested_contract?(val._)))
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# Contract for the Mon::Lazy monad, for use with the Contracts gem
|
2
|
+
# For example:
|
3
|
+
# <tt>Contract C::Lazy[Num] => C::Lazy[Num]
|
4
|
+
# def lazySquare(l)
|
5
|
+
# l.bind { |i| i * i }
|
6
|
+
# end</tt>
|
7
|
+
# will work only for (eg) lazySquare(Mon::Lazy.eventually { perform_painful_op_only_if_necessary() }).
|
8
|
+
# If passed anything (or returning anything) other than a Mon::Lazy, a
|
9
|
+
# ContractViolation will be thrown. <b>Note:</b> since Ruby is dynamically typed,
|
10
|
+
# we can't ensure that the eventual value will be of the correct type. So:
|
11
|
+
# lazySquare(Mon::Lazy.eventually { op_returning_string }) is likely
|
12
|
+
# to succeed, and fail when the operation is actually executed.
|
13
|
+
# Finalized Lazy monads don't have this issue.
|
14
|
+
|
15
|
+
module Mon
|
16
|
+
module Contract
|
17
|
+
require_relative 'monad_contract'
|
18
|
+
|
19
|
+
class Lazy < MonadContract
|
20
|
+
def valid?(val)
|
21
|
+
# Should be a Lazy (i.e. pending or final), and if it's
|
22
|
+
# final, the value should be valid re: the provided contract
|
23
|
+
val.is_a?(Mon::M::Lazy) and (val.is_a?(Mon::M::Final).implies(valid_nested_contract?(val._)))
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# Contract for the Mon::List monad, for use with the Contracts gem
|
2
|
+
# For example:
|
3
|
+
# <tt>Contract C::List[Num] => C::List[Num]
|
4
|
+
# def listSquare(l)
|
5
|
+
# l.bind { |i| i * i }
|
6
|
+
# end</tt>
|
7
|
+
# will work only for listSquare(Mon::List[1, 2, 3, 4]). If passed
|
8
|
+
# anything (or returning anything) other than a Mon::List, a
|
9
|
+
# ContractViolation will be thrown.
|
10
|
+
|
11
|
+
module Mon
|
12
|
+
module Contract
|
13
|
+
require_relative 'monad_contract'
|
14
|
+
|
15
|
+
class List < MonadContract
|
16
|
+
def valid?(val)
|
17
|
+
# Should be a List whose elements satisfy the given contract
|
18
|
+
val.is_a?(Mon::M::List) and (val._.all? { |el| valid_nested_contract?(el) })
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
|
2
|
+
module Mon
|
3
|
+
module Contract
|
4
|
+
require_relative 'monad_contract'
|
5
|
+
|
6
|
+
class Maybe < MonadContract
|
7
|
+
|
8
|
+
def valid?(val)
|
9
|
+
# Should either be None or valid?
|
10
|
+
val.is_a?(Mon::M::Maybe) and (val.is_a?(Mon::M::Some).implies(valid_nested_contract?(val._)))
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
|
2
|
+
module Mon
|
3
|
+
module Contract
|
4
|
+
require_relative '../mon.rb'
|
5
|
+
require 'contracts'
|
6
|
+
|
7
|
+
class MonadContract < Contracts::CallableClass
|
8
|
+
require_relative 'contract_helpers'
|
9
|
+
|
10
|
+
def initialize(*vals)
|
11
|
+
if vals.length != 1
|
12
|
+
throw ArgumentError.new("Incorrect usage of #{ self.class.name } contract, should be #{ self.class.name }[<contract>]")
|
13
|
+
end
|
14
|
+
@nested_contract = vals[0]
|
15
|
+
end
|
16
|
+
|
17
|
+
def nested_contract
|
18
|
+
@nested_contract
|
19
|
+
end
|
20
|
+
|
21
|
+
def valid?(val)
|
22
|
+
throw RuntimeError.new("MonadContract is abstract, #valid? must be overridden!")
|
23
|
+
end
|
24
|
+
|
25
|
+
def valid_nested_contract?(val)
|
26
|
+
nested_validator = Object::Contract::make_validator(@nested_contract)
|
27
|
+
nested_validator.call(val)
|
28
|
+
end
|
29
|
+
|
30
|
+
def to_s
|
31
|
+
"#{ self.class.name }[#{@nested_contract.to_s}]"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
class Monad < MonadContract
|
36
|
+
def valid?(val)
|
37
|
+
return false unless (val.is_a? Mon::M::Monad)
|
38
|
+
case val
|
39
|
+
when Mon::M::List
|
40
|
+
Mon::C::List.new(@nested_contract).valid?(val)
|
41
|
+
when Mon::M::Maybe
|
42
|
+
Mon::C::Maybe.new(@nested_contract).valid?(val)
|
43
|
+
when Mon::M::Try
|
44
|
+
Mon::C::Try.new(@nested_contract).valid?(val)
|
45
|
+
when Mon::M::Lazy
|
46
|
+
Mon::C::Lazy.new(@nested_contract).valid?(val)
|
47
|
+
when Mon::M::Future
|
48
|
+
Mon::C::Future.new(@nested_contract).valid?(val)
|
49
|
+
when Mon::M::React
|
50
|
+
Mon::C::React.new(@nested_contract).valid?(val)
|
51
|
+
else
|
52
|
+
raise RuntimeError.new("Unrecognized monad: #{ val.class }!")
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# Contract for the Mon::React monad, for use with the Contracts gem
|
2
|
+
# For example:
|
3
|
+
# <tt>Contract C::React[Num] => C::React[Num]
|
4
|
+
# def reactSquare(l)
|
5
|
+
# l.bind { |i| i * i }
|
6
|
+
# end</tt>
|
7
|
+
# will work only for (eg) reactSquare(Mon::React[3]).
|
8
|
+
# If passed anything (or returning anything) other than a Mon::React, a
|
9
|
+
# ContractViolation will be thrown.
|
10
|
+
|
11
|
+
module Mon
|
12
|
+
module Contract
|
13
|
+
require_relative 'monad_contract'
|
14
|
+
|
15
|
+
class React < MonadContract
|
16
|
+
def valid?(val)
|
17
|
+
# Should be a React (i.e. success or failure), and if it's a
|
18
|
+
# success, the value should be valid re: the provided contract
|
19
|
+
val.is_a?(Mon::M::React) and (valid_nested_contract?(val._))
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# Contract for the Mon::Try monad, for use with the Contracts gem
|
2
|
+
# For example:
|
3
|
+
# <tt>Contract C::Try[Num] => C::Try[Num]
|
4
|
+
# def trySquare(l)
|
5
|
+
# l.bind { |i| i * i }
|
6
|
+
# end</tt>
|
7
|
+
# will work only for (eg) trySquare(Mon::Try.to { get_num_from_remote_service() }). If passed
|
8
|
+
# anything (or returning anything) other than a Mon::Try, a
|
9
|
+
# ContractViolation will be thrown.
|
10
|
+
|
11
|
+
module Mon
|
12
|
+
module Contract
|
13
|
+
require_relative 'monad_contract'
|
14
|
+
|
15
|
+
class Try < MonadContract
|
16
|
+
def valid?(val)
|
17
|
+
# Should be a Try (i.e. success or failure), and if it's a
|
18
|
+
# success, the value should be valid re: the provided contract
|
19
|
+
val.is_a?(Mon::M::Try) and (val.is_a?(Mon::M::Success).implies(valid_nested_contract?(val._)))
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
data/lib/mon.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
|
2
|
+
module Mon
|
3
|
+
require_relative 'monads/list'
|
4
|
+
require_relative 'monads/try'
|
5
|
+
require_relative 'monads/maybe'
|
6
|
+
require_relative 'monads/lazy'
|
7
|
+
require_relative 'monads/future'
|
8
|
+
require_relative 'monads/reactron'
|
9
|
+
|
10
|
+
require_relative 'contracts/list'
|
11
|
+
require_relative 'contracts/try'
|
12
|
+
require_relative 'contracts/maybe'
|
13
|
+
require_relative 'contracts/lazy'
|
14
|
+
require_relative 'contracts/future'
|
15
|
+
require_relative 'contracts/reactron'
|
16
|
+
require_relative 'contracts/monad_contract'
|
17
|
+
|
18
|
+
# Namespace aliases
|
19
|
+
C = Contract
|
20
|
+
M = Monad
|
21
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# Base class for Mon monads. Do not instantiate.
|
2
|
+
|
3
|
+
module Mon
|
4
|
+
module Monad
|
5
|
+
module ChainableMonad
|
6
|
+
def method_missing(name, *args, &fun)
|
7
|
+
self.bind { |o| o.send(name, *args, &fun) }
|
8
|
+
end
|
9
|
+
|
10
|
+
def respond_to? name
|
11
|
+
self._canBind?(name)
|
12
|
+
end
|
13
|
+
|
14
|
+
def _
|
15
|
+
self.unwrap
|
16
|
+
end
|
17
|
+
|
18
|
+
def coerce(other)
|
19
|
+
return self, other
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,154 @@
|
|
1
|
+
# Future wraps an asynchronous process and acts as a placeholder.
|
2
|
+
#
|
3
|
+
# A simple example:
|
4
|
+
# <tt>f = Future.eventually { call_remote_web_service }
|
5
|
+
# data = f.bind { |response| response.getContent }
|
6
|
+
# while data.pending?
|
7
|
+
# puts "Still waiting..."
|
8
|
+
# sleep 1
|
9
|
+
# end
|
10
|
+
# puts "Got a response: #{ data.unwrap }" </tt>
|
11
|
+
#
|
12
|
+
# Rather than explicitly waiting, you can always block on an answer:
|
13
|
+
# <tt>data = f.bind { |r| r.getContent }
|
14
|
+
# puts "Got a response: #{ data.finalize.unwrap }"</tt>
|
15
|
+
|
16
|
+
module Mon
|
17
|
+
|
18
|
+
module Monad
|
19
|
+
|
20
|
+
require_relative 'chainable_monad'
|
21
|
+
require_relative 'monad'
|
22
|
+
|
23
|
+
class Future < Monad
|
24
|
+
include ChainableMonad
|
25
|
+
|
26
|
+
# Create a Future, executing some function, with optional arguments.
|
27
|
+
# Either:
|
28
|
+
# <tt>value = Future::eventually { do_something_slow }</tt>
|
29
|
+
# Or:
|
30
|
+
# <tt>value = Future::eventually(myValue) { |val| do_something_slow(val) }</tt>
|
31
|
+
def self::eventually *args, &fun
|
32
|
+
FuturePromise::perform(fun, args)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Wrap an object in a Future. It will start off as complete, but functions that you bind
|
36
|
+
# to it will be asynchronous.
|
37
|
+
# <tt>value = Future["some_username"]
|
38
|
+
# futureBirthday = value.bind { |username| db.fetchUserInfo(username).getBirthday } # Returns a FuturePromise, wrapping an inflight thread
|
39
|
+
# puts "We have a birthday for some_username: #{ futureBirthday.unwrap }"</tt>
|
40
|
+
def self::[](obj)
|
41
|
+
FutureComplete[obj]
|
42
|
+
end
|
43
|
+
|
44
|
+
def eql? o
|
45
|
+
# Time to collapse
|
46
|
+
if o.is_a? Future
|
47
|
+
self.unwrap == o.unwrap
|
48
|
+
else
|
49
|
+
self.unwrap == o
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def equals? o
|
54
|
+
eql? o
|
55
|
+
end
|
56
|
+
|
57
|
+
def == o
|
58
|
+
eql? o
|
59
|
+
end
|
60
|
+
|
61
|
+
# For use with contracts, DEPRECATED
|
62
|
+
def self::valid?(v)
|
63
|
+
v.is_a?(Mon::Future)
|
64
|
+
end
|
65
|
+
|
66
|
+
class << self
|
67
|
+
protected :new
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# FuturePromise represents an asynchronous operation that is still inflight,
|
72
|
+
# or complete.
|
73
|
+
class FuturePromise < Future
|
74
|
+
def initialize(thread)
|
75
|
+
@thread = thread
|
76
|
+
end
|
77
|
+
|
78
|
+
# Equivalent to calling Future::eventually { ... }
|
79
|
+
def self::perform(fun, args)
|
80
|
+
if args.length == 1 and args[0].is_a? FuturePromise
|
81
|
+
# We're waiting on something, but we don't want to block
|
82
|
+
FuturePromise.new(Thread.new { fun.call(args[0].unwrap) })
|
83
|
+
else
|
84
|
+
FuturePromise.new(Thread.new(args) { |args| fun.call(*args) })
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# Take a block, and apply it to the value asynchronously, returing
|
89
|
+
# a future to represent the result
|
90
|
+
def bind &fun
|
91
|
+
FuturePromise::perform(fun, [self])
|
92
|
+
end
|
93
|
+
|
94
|
+
# Block, then return the result (unwrapped)
|
95
|
+
def unwrap
|
96
|
+
@thread.value
|
97
|
+
end
|
98
|
+
|
99
|
+
# Block until the operation completes, then return the result (wrapped as a FutureComplete)
|
100
|
+
def finalize
|
101
|
+
value = @thread.value
|
102
|
+
FutureComplete[(value.is_a? Future) ? value.unwrap : value]
|
103
|
+
end
|
104
|
+
|
105
|
+
# Are we still inflight?
|
106
|
+
def pending?
|
107
|
+
@thread.alive?
|
108
|
+
end
|
109
|
+
|
110
|
+
def to_s
|
111
|
+
"FuturePromise[#{ @thread }]"
|
112
|
+
end
|
113
|
+
|
114
|
+
class << self
|
115
|
+
protected :new
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
# FutureComplete represents a finalized value.
|
120
|
+
class FutureComplete < Future
|
121
|
+
def initialize(value)
|
122
|
+
@value = value
|
123
|
+
end
|
124
|
+
|
125
|
+
# You should probably be using Future[...] instead.
|
126
|
+
def self::[](value)
|
127
|
+
self::new(value)
|
128
|
+
end
|
129
|
+
|
130
|
+
# Asyrchronously apply fun to the value wrapped by this FutureComplete. Returns a FuturePromise.
|
131
|
+
def bind &fun
|
132
|
+
FuturePromise::perform(fun, [@value])
|
133
|
+
end
|
134
|
+
|
135
|
+
def pending?
|
136
|
+
false
|
137
|
+
end
|
138
|
+
|
139
|
+
def unwrap
|
140
|
+
@value
|
141
|
+
end
|
142
|
+
|
143
|
+
def to_s
|
144
|
+
"FutureComplete[#{ @value }]"
|
145
|
+
end
|
146
|
+
|
147
|
+
class << self
|
148
|
+
protected :new
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
end
|
153
|
+
|
154
|
+
end
|
data/lib/monads/lazy.rb
ADDED
@@ -0,0 +1,200 @@
|
|
1
|
+
# Lazy wraps computations which will only be performed on-demand.
|
2
|
+
#
|
3
|
+
# A simple example:
|
4
|
+
# <tt>l = Lazy.eventually(n) { perform_some_slow_operation(n) }
|
5
|
+
# data = f.bind { |result| some_even_more_costly_operation(result) }
|
6
|
+
# # At this point, no real computation has taken place
|
7
|
+
# puts "Here's the result: #{ data.unwrap } # ouch!</tt>
|
8
|
+
#
|
9
|
+
# Why would you ever want to do this? Here's an example:
|
10
|
+
# <tt>factNums = (0..100000).map { |i| Lazy[i] }.map { |m| m.bind { |i| i.factorial } }
|
11
|
+
# # We now have a list of 100000 'potential' factorial numbers, but
|
12
|
+
# # we've done this very quickly: we haven't actually done any math yet.
|
13
|
+
# fiveThousandFact = factNums[5000].unwrap
|
14
|
+
# # We've performed exactly one factorial operation! And yet we can pass around
|
15
|
+
# # and use factNums as if it really was a list of factorials. Neat!</tt>
|
16
|
+
|
17
|
+
module Mon
|
18
|
+
|
19
|
+
module Monad
|
20
|
+
|
21
|
+
require_relative 'chainable_monad'
|
22
|
+
require_relative 'monad'
|
23
|
+
|
24
|
+
# Lazy is the parent class of Pending and Final, the two states a Lazy monad can be in.
|
25
|
+
# Use with:
|
26
|
+
# <tt>lazyValue = Lazy[5] # Seems pointless so far...
|
27
|
+
# lazyCalc = lazyValue.bind { |i| (0..i).map { |n| n.factorial } }.bind { |factlist| factlist.map { |i| i * i }.... # Keep right on going!
|
28
|
+
# # We still haven't done any work!
|
29
|
+
# puts lazyCalc.unwrap # Time to have a nap...</tt>
|
30
|
+
# Or:
|
31
|
+
# lazyProc = Lazy.eventually(5) { (0..5).map { |i| call_some_remote_service_ondemand(i) } }
|
32
|
+
# # Haven't done anything yet...
|
33
|
+
# lazyProc.sample.unwrap.map { |v| "A random response: #{ v }" } # Do one of 5 possible service calls</tt>
|
34
|
+
class Lazy < Monad
|
35
|
+
include ChainableMonad
|
36
|
+
|
37
|
+
# Wrap a value in Lazy
|
38
|
+
def self::[](obj = nil)
|
39
|
+
if obj.is_a? Proc
|
40
|
+
eventually(obj)
|
41
|
+
else
|
42
|
+
Final[obj]
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# Perform an operation, if necessary:
|
47
|
+
# <tt>Lazy.eventually { 10 * 10 }</tt>
|
48
|
+
# Or:
|
49
|
+
# <tt>Lazy.eventually(10) { |n| n * 10 }</tt>
|
50
|
+
def self::eventually(*args, &fun)
|
51
|
+
Pending::eventually(fun, args)
|
52
|
+
end
|
53
|
+
|
54
|
+
# For contracts. Deprecated!
|
55
|
+
def self::valid?(v)
|
56
|
+
v.is_a? Mon::Lazy
|
57
|
+
end
|
58
|
+
|
59
|
+
class << self
|
60
|
+
protected :new
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# Final represents a finalized lazy value (i.e. a value with no pending ops).
|
65
|
+
class Final < Lazy
|
66
|
+
def initialize(obj)
|
67
|
+
@obj = obj
|
68
|
+
end
|
69
|
+
|
70
|
+
# You probably want Lazy[...]
|
71
|
+
def self::[](obj = nil)
|
72
|
+
Final.new(obj)
|
73
|
+
end
|
74
|
+
|
75
|
+
# Add an operation to be performed on the wrapped value, only if necessary
|
76
|
+
def bind(&fun)
|
77
|
+
Pending::eventually(fun, [@obj])
|
78
|
+
end
|
79
|
+
|
80
|
+
# Perform any pending lazy operations
|
81
|
+
def finalize
|
82
|
+
self
|
83
|
+
end
|
84
|
+
|
85
|
+
def _canBind?(name)
|
86
|
+
@obj.respond_to? name
|
87
|
+
end
|
88
|
+
|
89
|
+
def _finalized?
|
90
|
+
true
|
91
|
+
end
|
92
|
+
|
93
|
+
# Perform any pending lazy operations and unwrap the result
|
94
|
+
def unwrap
|
95
|
+
@obj
|
96
|
+
end
|
97
|
+
|
98
|
+
# Alias for #unwrap
|
99
|
+
def _
|
100
|
+
unwrap
|
101
|
+
end
|
102
|
+
|
103
|
+
def to_s
|
104
|
+
"Final[#{ @obj }]"
|
105
|
+
end
|
106
|
+
|
107
|
+
def eql? o
|
108
|
+
# Time to collapse
|
109
|
+
if o.is_a? Lazy
|
110
|
+
self.unwrap == o.unwrap
|
111
|
+
else
|
112
|
+
self.unwrap == o
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def equals? o
|
117
|
+
eql? o
|
118
|
+
end
|
119
|
+
|
120
|
+
def == o
|
121
|
+
eql? o
|
122
|
+
end
|
123
|
+
|
124
|
+
class << self
|
125
|
+
protected :new
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
# Pending represents a Lazy value with pending operations. Unwrapping or finalizing with trigger
|
130
|
+
# said pending operations.
|
131
|
+
class Pending < Lazy
|
132
|
+
def initialize(fun, target = nil)
|
133
|
+
case fun.arity
|
134
|
+
when 1 then @fun = lambda { fun.call(target.unwrap) }
|
135
|
+
when 0 then @fun = fun
|
136
|
+
else raise ArgumentError.new("Bad function passed to #{ self }")
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
# Add an operation to be performed on the wrapped value, only if necessary
|
141
|
+
def bind(&fun)
|
142
|
+
Pending::eventually(fun, [self])
|
143
|
+
end
|
144
|
+
|
145
|
+
def _canBind?(name)
|
146
|
+
true
|
147
|
+
end
|
148
|
+
|
149
|
+
# Complete any pending operations and return the result, wrapped in a Final.
|
150
|
+
# Eg: <tt>Lazy(10).bind { |i| i * i }.finalize # ==> Final[100]</tt>
|
151
|
+
def finalize
|
152
|
+
Final[unwrap]
|
153
|
+
end
|
154
|
+
|
155
|
+
# Complete any pending operations and return the result, unwrapped.
|
156
|
+
# Eg: <tt>Lazy(10).bind { |i| i * i }.unwrap # ==> 100</tt>
|
157
|
+
def unwrap
|
158
|
+
@fun.call
|
159
|
+
end
|
160
|
+
|
161
|
+
# Alias for #unwrap
|
162
|
+
def _
|
163
|
+
unwrap
|
164
|
+
end
|
165
|
+
|
166
|
+
# Perform an operation, if necessary:
|
167
|
+
# <tt>Lazy.eventually { 10 * 10 }</tt>
|
168
|
+
# Or:
|
169
|
+
# <tt>Lazy.eventually(10) { |n| n * 10 }</tt>
|
170
|
+
def self::eventually fun, args
|
171
|
+
Pending.new(lambda { fun.call(*args.map { |v| (v.is_a? Lazy) ? v.unwrap : v }) })
|
172
|
+
end
|
173
|
+
|
174
|
+
def to_s
|
175
|
+
"Pending[#{ @fun }]"
|
176
|
+
end
|
177
|
+
|
178
|
+
def eql? o
|
179
|
+
# Time to collapse
|
180
|
+
if o.is_a? Lazy
|
181
|
+
self.unwrap == o.unwrap
|
182
|
+
else
|
183
|
+
self.unwrap == o
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
def equals? o
|
188
|
+
eql? o
|
189
|
+
end
|
190
|
+
|
191
|
+
def == o
|
192
|
+
eql? o
|
193
|
+
end
|
194
|
+
|
195
|
+
class << self
|
196
|
+
protected :new
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|