mon 0.0.2
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 +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
|