immutable 0.1.0 → 0.2.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.
- data/README.md +33 -23
- data/benchmark/benchmark_queue.rb +41 -0
- data/lib/immutable.rb +9 -0
- data/lib/immutable/foldable.rb +29 -0
- data/lib/immutable/list.rb +247 -77
- data/lib/immutable/map.rb +18 -20
- data/lib/immutable/promise.rb +116 -0
- data/lib/immutable/queue.rb +100 -0
- data/lib/immutable/stream.rb +564 -0
- data/test/immutable/test_list.rb +50 -2
- data/test/immutable/test_map.rb +15 -2
- data/test/immutable/test_promise.rb +202 -0
- data/test/immutable/test_queue.rb +43 -0
- data/test/immutable/test_stream.rb +359 -0
- data/test/test_helper.rb +14 -0
- metadata +12 -2
data/lib/immutable/map.rb
CHANGED
@@ -1,21 +1,20 @@
|
|
1
|
-
|
2
|
-
# ported from http://www.cs.kent.ac.uk/people/staff/smk/redblack/Untyped.hs
|
1
|
+
require "immutable/foldable"
|
3
2
|
|
4
3
|
module Immutable
|
5
|
-
#
|
4
|
+
# +Immutable::Map+ represents an immutable map from keys to
|
6
5
|
# values.
|
7
6
|
#
|
8
|
-
#
|
9
|
-
#
|
10
|
-
#
|
7
|
+
# +Immutable::Map+ is an abstract class and
|
8
|
+
# {Immutable::Map.[]} should be used instead of
|
9
|
+
# {Immutable::Map.new}. For example:
|
11
10
|
#
|
12
11
|
# include Immutable
|
13
12
|
# p Map[] #=> Map[]
|
14
13
|
# p Map[a: 1, b: 2] #=> Map[:a => 1, :b => 2]
|
15
14
|
#
|
16
|
-
#
|
17
|
-
# returns a new
|
18
|
-
# changed by
|
15
|
+
# {#insert} inserts a key/value pair and
|
16
|
+
# returns a new +Immutable::Map+. The original map never be
|
17
|
+
# changed by {#insert}. For example:
|
19
18
|
#
|
20
19
|
# m = Map[a: 1]
|
21
20
|
# p m #=> Map[:a => 1]
|
@@ -23,6 +22,9 @@ module Immutable
|
|
23
22
|
# p m2 #=> Map[:a => 1, :b => 2]
|
24
23
|
# p m #=> Map[:a => 1]
|
25
24
|
class Map
|
25
|
+
include Enumerable
|
26
|
+
include Foldable
|
27
|
+
|
26
28
|
class InvarianceViolationError < StandardError
|
27
29
|
end
|
28
30
|
|
@@ -38,7 +40,7 @@ module Immutable
|
|
38
40
|
end
|
39
41
|
|
40
42
|
# Returns a map that has the same key/value pairs as the
|
41
|
-
#
|
43
|
+
# +Hash+ object +h+.
|
42
44
|
def self.[](h = {})
|
43
45
|
h.inject(Leaf) { |m, (k, v)| m.insert(k, v) }
|
44
46
|
end
|
@@ -48,7 +50,7 @@ module Immutable
|
|
48
50
|
ins(key, value).make_black
|
49
51
|
end
|
50
52
|
|
51
|
-
# Returns the value at +key+ in +self+, or
|
53
|
+
# Returns the value at +key+ in +self+, or +nil+ if +key+
|
52
54
|
# isn't in +self+.
|
53
55
|
def [](key)
|
54
56
|
raise ScriptError, "this method should be overriden"
|
@@ -75,6 +77,11 @@ module Immutable
|
|
75
77
|
} + "]"
|
76
78
|
end
|
77
79
|
|
80
|
+
# Calls +block+ once for each key/value in +self+.
|
81
|
+
def each(&block)
|
82
|
+
foldl_with_key(nil) { |x, k, v| yield([k, v]) }
|
83
|
+
end
|
84
|
+
|
78
85
|
# Folds the values in +self+ from right to left.
|
79
86
|
def foldr(e)
|
80
87
|
foldr_with_key(e) { |k, v, x| yield(v, x) }
|
@@ -122,9 +129,6 @@ module Immutable
|
|
122
129
|
Leaf
|
123
130
|
end
|
124
131
|
|
125
|
-
def Leaf.each
|
126
|
-
end
|
127
|
-
|
128
132
|
class Fork < Map #:nodoc:
|
129
133
|
attr_reader :left, :key, :value, :right
|
130
134
|
|
@@ -165,12 +169,6 @@ module Immutable
|
|
165
169
|
end
|
166
170
|
end
|
167
171
|
|
168
|
-
def each(&block)
|
169
|
-
left.each(&block)
|
170
|
-
yield key, value
|
171
|
-
right.each(&block)
|
172
|
-
end
|
173
|
-
|
174
172
|
def Leaf.foldr_with_key(e)
|
175
173
|
e
|
176
174
|
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
module Immutable
|
2
|
+
# +Immutable::Promise+ represents a promise to evaluate an expression
|
3
|
+
# later.
|
4
|
+
#
|
5
|
+
# @example Delayed computation
|
6
|
+
# promise = Promise.delay { puts "hello"; 1 + 2 }
|
7
|
+
# x = promise.force #=> hello
|
8
|
+
# p x #=> 3
|
9
|
+
# y = promise.force #=> (no output; the value is memoized)
|
10
|
+
# p y #=> 3
|
11
|
+
# @example Infinite streams
|
12
|
+
# def from(n)
|
13
|
+
# Promise.delay {
|
14
|
+
# Cons[n, from(n + 1)]
|
15
|
+
# }
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# def stream_ref(s, n)
|
19
|
+
# xs = s.force
|
20
|
+
# if xs.empty?
|
21
|
+
# nil
|
22
|
+
# else
|
23
|
+
# n == 0 ? xs.head : stream_ref(xs.tail, n - 1)
|
24
|
+
# end
|
25
|
+
# end
|
26
|
+
#
|
27
|
+
# nats = from(0)
|
28
|
+
# p stream_ref(nats, 0) #=> 0
|
29
|
+
# p stream_ref(nats, 3) #=> 3
|
30
|
+
class Promise
|
31
|
+
# :nodoc:
|
32
|
+
Content = Struct.new(:type, :value)
|
33
|
+
|
34
|
+
def initialize(type, value)
|
35
|
+
@content = Content.new(type, value)
|
36
|
+
end
|
37
|
+
|
38
|
+
private_class_method :new
|
39
|
+
|
40
|
+
# Takes a block which evaluates to a promise, and returns a promise
|
41
|
+
# which at some point in the future may be asked (by +Promise#force+)
|
42
|
+
# to evaluate the block and deliver the value of the resulting promise.
|
43
|
+
#
|
44
|
+
# @return [Promise] the created promise.
|
45
|
+
def self.lazy(&block)
|
46
|
+
new(:lazy, block)
|
47
|
+
end
|
48
|
+
|
49
|
+
# Takes an argument, and returns a promise which deliver the value of
|
50
|
+
# the argument.
|
51
|
+
#
|
52
|
+
# <code>Promise.eager(expresion)</code> is equivalent to
|
53
|
+
# <code>(value = Promise.eager; Promise.delay { value })</code>.
|
54
|
+
#
|
55
|
+
# @param [Object] value the value to be returned by +Promise#force+.
|
56
|
+
# @return [Promise] the created promise.
|
57
|
+
def self.eager(value)
|
58
|
+
new(:eager, value)
|
59
|
+
end
|
60
|
+
|
61
|
+
# Returns whether +self+ is lazy.
|
62
|
+
#
|
63
|
+
# @return [true, false] +true+ if +self+ is lazy; otherwise, +false+.
|
64
|
+
def lazy?
|
65
|
+
content.type == :lazy
|
66
|
+
end
|
67
|
+
|
68
|
+
# Returns whether +self+ is eager.
|
69
|
+
#
|
70
|
+
# @return [true, false] +true+ if +self+ is eager; otherwise, +false+.
|
71
|
+
def eager?
|
72
|
+
content.type == :eager
|
73
|
+
end
|
74
|
+
|
75
|
+
# Takes a block, and returns a promise which at some point in the future
|
76
|
+
# may be asked (by +Promise#force+) to evaluate the block and deliver
|
77
|
+
# the resulting value.
|
78
|
+
#
|
79
|
+
# <code>Promise.delay { expression }</code> is equivalent to
|
80
|
+
# <code>Promise.lazy { Promise.eager(expression) }</code>.
|
81
|
+
#
|
82
|
+
# @return [Promise] the created promise.
|
83
|
+
def self.delay
|
84
|
+
lazy {
|
85
|
+
eager(yield)
|
86
|
+
}
|
87
|
+
end
|
88
|
+
|
89
|
+
# Returns the value of +self+.
|
90
|
+
# If a value of +self+ has already been computated, the value is
|
91
|
+
# returned. Otherwise, the promise is first evaluated, and the resulting
|
92
|
+
# value is returned.
|
93
|
+
#
|
94
|
+
# @return [Object] the value of +self+.
|
95
|
+
def force
|
96
|
+
case content.type
|
97
|
+
when :eager
|
98
|
+
content.value
|
99
|
+
when :lazy
|
100
|
+
promise = content.value.call
|
101
|
+
if content.type != :eager
|
102
|
+
content.type = promise.content.type
|
103
|
+
content.value = promise.content.value
|
104
|
+
promise.content = content
|
105
|
+
end
|
106
|
+
force
|
107
|
+
else
|
108
|
+
raise ScriptError, "should not reach here"
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
protected
|
113
|
+
|
114
|
+
attr_accessor :content
|
115
|
+
end
|
116
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
require "immutable/stream"
|
2
|
+
|
3
|
+
module Immutable
|
4
|
+
# +Immutable::Queue+ is an implementation of real-time queues described in
|
5
|
+
# "Purely Functional Data Structures" by Chris Okasaki.
|
6
|
+
class Queue
|
7
|
+
include Enumerable
|
8
|
+
|
9
|
+
# +Queue.new+ is for internal use only. Use {Queue.empty} or {Queue.[]}
|
10
|
+
# instead.
|
11
|
+
def initialize(front, rear, schedule)
|
12
|
+
@front = front
|
13
|
+
@rear = rear
|
14
|
+
@schedule = schedule
|
15
|
+
end
|
16
|
+
|
17
|
+
# Returns an empty queue.
|
18
|
+
#
|
19
|
+
# @return [Queue] the empty queue.
|
20
|
+
def self.empty
|
21
|
+
Queue.new(Stream.null, Nil, Stream.null)
|
22
|
+
end
|
23
|
+
|
24
|
+
# Creates a new queue populated with the given objects.
|
25
|
+
#
|
26
|
+
# @param [Array<Object>] elements the elements of the queue.
|
27
|
+
# @return [List] the new queue.
|
28
|
+
def self.[](*elements)
|
29
|
+
elements.inject(empty, &:snoc)
|
30
|
+
end
|
31
|
+
|
32
|
+
# Returns whether +self+ is empty.
|
33
|
+
#
|
34
|
+
# @return [true, false] +true+ if +self+ is empty; otherwise, +false+.
|
35
|
+
def empty?
|
36
|
+
@front.null?
|
37
|
+
end
|
38
|
+
|
39
|
+
def rotate(front, rear, accumulator)
|
40
|
+
Stream.lazy {
|
41
|
+
if front.null?
|
42
|
+
Stream.cons(->{rear.head}, ->{accumulator})
|
43
|
+
else
|
44
|
+
Stream.cons(->{front.head}, ->{
|
45
|
+
rotate(front.tail, rear.tail,
|
46
|
+
Stream.cons(->{rear.head}, ->{accumulator}))
|
47
|
+
})
|
48
|
+
end
|
49
|
+
}
|
50
|
+
end
|
51
|
+
private :rotate
|
52
|
+
|
53
|
+
def queue(front, rear, schedule)
|
54
|
+
if schedule.null?
|
55
|
+
f = rotate(front, rear, Stream.null)
|
56
|
+
Queue.new(f, Nil, f)
|
57
|
+
else
|
58
|
+
Queue.new(front, rear, schedule.tail)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
private :queue
|
62
|
+
|
63
|
+
# Adds a new element at the end of +self+.
|
64
|
+
#
|
65
|
+
# @param [Object] x the element to add.
|
66
|
+
# @return [Queue] a new queue.
|
67
|
+
def snoc(x)
|
68
|
+
queue(@front, Cons[x, @rear], @schedule)
|
69
|
+
end
|
70
|
+
alias push snoc
|
71
|
+
|
72
|
+
# Returns the first element of +self+. If +self+ is empty,
|
73
|
+
# +Immutable::List::EmptyError+ is raised.
|
74
|
+
#
|
75
|
+
# @return [Object] the first element of +self+.
|
76
|
+
def head
|
77
|
+
@front.head
|
78
|
+
end
|
79
|
+
|
80
|
+
# Returns the elements after the head of +self+. If +self+ is empty,
|
81
|
+
# +Immutable::List::EmptyError+ is raised.
|
82
|
+
#
|
83
|
+
# @return [Queue] the elements after the head of +self+.
|
84
|
+
def tail
|
85
|
+
if @front.null?
|
86
|
+
raise List::EmptyError
|
87
|
+
else
|
88
|
+
queue(@front.tail, @rear, @schedule)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
# Calls +block+ once for each element in +self+.
|
93
|
+
def each(&block)
|
94
|
+
unless @front.null?
|
95
|
+
yield(head)
|
96
|
+
tail.each(&block)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,564 @@
|
|
1
|
+
require "immutable/foldable"
|
2
|
+
require "immutable/list"
|
3
|
+
require "immutable/promise"
|
4
|
+
|
5
|
+
module Immutable
|
6
|
+
# +Immutable::Stream+ represents a stream, also known as a lazy list.
|
7
|
+
# A stream is similar to a list. However the evaluation of a stream
|
8
|
+
# element is delayed until its value is needed
|
9
|
+
#
|
10
|
+
# @example Natural numbers
|
11
|
+
# def from(n)
|
12
|
+
# Stream.cons ->{n}, ->{from(n + 1)}
|
13
|
+
# end
|
14
|
+
#
|
15
|
+
# nats = from(0)
|
16
|
+
# p from(0).take(10).to_list #=> List[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
|
17
|
+
# @example Prime numbers
|
18
|
+
# primes = Stream.from(2).filter { |n|
|
19
|
+
# (2 ... n / 2 + 1).all? { |x|
|
20
|
+
# n % x != 0
|
21
|
+
# }
|
22
|
+
# }
|
23
|
+
# p primes.take_while { |n| n < 10 }.to_list #=> List[2, 3, 5, 7]
|
24
|
+
class Stream < Promise
|
25
|
+
include Enumerable
|
26
|
+
include Foldable
|
27
|
+
|
28
|
+
NULL = Object.new
|
29
|
+
|
30
|
+
def NULL.head
|
31
|
+
raise List::EmptyError
|
32
|
+
end
|
33
|
+
|
34
|
+
def NULL.tail
|
35
|
+
raise List::EmptyError
|
36
|
+
end
|
37
|
+
|
38
|
+
def NULL.inspect
|
39
|
+
"NULL"
|
40
|
+
end
|
41
|
+
|
42
|
+
class Pair
|
43
|
+
attr_reader :head, :tail
|
44
|
+
|
45
|
+
def initialize(head, tail)
|
46
|
+
@head = head
|
47
|
+
@tail = tail
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# Returns an empty stream.
|
52
|
+
#
|
53
|
+
# @return [Stream] an empty stream.
|
54
|
+
def self.null
|
55
|
+
delay { NULL }
|
56
|
+
end
|
57
|
+
|
58
|
+
# Creates a new stream.
|
59
|
+
#
|
60
|
+
# @example A Stream which has 123 as the only element.
|
61
|
+
# s = Stream.cons(->{123}, ->{Stream.null})
|
62
|
+
# p s.to_list #=> List[123]
|
63
|
+
# @example A Stream which has two elements: "abc" and "def".
|
64
|
+
# s = Stream.cons(->{"abc"},
|
65
|
+
# ->{Stream.cons(->{"def"}, ->{Stream.null})})
|
66
|
+
# p s.to_list #=> List["abc", "def"]
|
67
|
+
#
|
68
|
+
# @param [Proc] head a +Proc+ whose value is the head of +self+.
|
69
|
+
# @param [Proc] tail a +Proc+ whose value is the tail of +self+.
|
70
|
+
# @return [Stream] the new stream.
|
71
|
+
def self.cons(head, tail)
|
72
|
+
Stream.eager(Pair.new(Stream.delay(&head), Stream.lazy(&tail)))
|
73
|
+
end
|
74
|
+
|
75
|
+
# Creates a new stream whose head is the value of +block+ and whose tail
|
76
|
+
# is +self+.
|
77
|
+
#
|
78
|
+
# @example A Stream which has 123 as the only element.
|
79
|
+
# s = Stream.null.prepend {123}
|
80
|
+
# p s.to_list #=> List[123]
|
81
|
+
# @example A Stream which has two elements: "abc" and "def".
|
82
|
+
# s = Stream.null.prepend {"def"}.prepend {"abc"}
|
83
|
+
# p s.to_list #=> List["abc", "def"]
|
84
|
+
#
|
85
|
+
# @return [Stream] the new stream.
|
86
|
+
def prepend(&block)
|
87
|
+
Stream.eager(Pair.new(Stream.delay(&block), self))
|
88
|
+
end
|
89
|
+
|
90
|
+
# Creates a new stream. Note that the arguments are evaluated eagerly.
|
91
|
+
#
|
92
|
+
# @param [Array<Object>] elements the elements of the stream.
|
93
|
+
# @return [Stream] the new stream.
|
94
|
+
def self.[](*elements)
|
95
|
+
from_enum(elements)
|
96
|
+
end
|
97
|
+
|
98
|
+
# Creates a new stream from an +Enumerable+ object.
|
99
|
+
#
|
100
|
+
# @param [Enumerable] e an +Enumerable+ object.
|
101
|
+
# @return [Stream] the new stream.
|
102
|
+
def self.from_enum(e)
|
103
|
+
from_enumerator(e.each)
|
104
|
+
end
|
105
|
+
|
106
|
+
# Creates a new stream from an +Enumerator+ object.
|
107
|
+
# Note that +from_enumerator+ has side effects because it calls
|
108
|
+
# +Enumerator#next+.
|
109
|
+
#
|
110
|
+
# @param [Enumerator] e an +Enumerator+ object.
|
111
|
+
# @return [Stream] the new stream.
|
112
|
+
def self.from_enumerator(e)
|
113
|
+
lazy {
|
114
|
+
begin
|
115
|
+
x = e.next
|
116
|
+
cons ->{ x }, ->{ from_enumerator(e) }
|
117
|
+
rescue StopIteration
|
118
|
+
null
|
119
|
+
end
|
120
|
+
}
|
121
|
+
end
|
122
|
+
|
123
|
+
# Creates an infinite stream which starts from +first+ and increments
|
124
|
+
# each succeeding element by +step+.
|
125
|
+
#
|
126
|
+
# @param [#+] first the first element.
|
127
|
+
# @param [#+] step the step for succeeding elements.
|
128
|
+
# @return [Stream] the new stream.
|
129
|
+
def self.from(first, step = 1)
|
130
|
+
cons ->{ first }, ->{ from(first + step, step) }
|
131
|
+
end
|
132
|
+
|
133
|
+
# Returns whether +self+ is empty.
|
134
|
+
#
|
135
|
+
# @return [true, false] +true+ if +self+ is empty; otherwise, +false+.
|
136
|
+
def null?
|
137
|
+
force == NULL
|
138
|
+
end
|
139
|
+
alias empty? null?
|
140
|
+
|
141
|
+
# Returns the first element of +self+. If +self+ is empty,
|
142
|
+
# +Immutable::List::EmptyError+ is raised.
|
143
|
+
#
|
144
|
+
# @return [Object] the first element of +self+.
|
145
|
+
def head
|
146
|
+
force.head.force
|
147
|
+
end
|
148
|
+
alias first head
|
149
|
+
|
150
|
+
# Returns the last element of +self+. If +self+ is empty,
|
151
|
+
# +Immutable::List::EmptyError+ is raised.
|
152
|
+
#
|
153
|
+
# @return [Object] the last element of +self+.
|
154
|
+
def last
|
155
|
+
if tail.null?
|
156
|
+
head
|
157
|
+
else
|
158
|
+
tail.last
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
# Returns the stream stored in the tail of +self+. If +self+ is empty,
|
163
|
+
# +Immutable::List::EmptyError+ is raised.
|
164
|
+
#
|
165
|
+
# @return [Stream] the stream stored in the tail of +self+.
|
166
|
+
def tail
|
167
|
+
force.tail
|
168
|
+
end
|
169
|
+
|
170
|
+
# Returns all the elements of +self+ except the last one.
|
171
|
+
# If +self+ is empty, +Immutable::List::EmptyError+ is
|
172
|
+
# raised.
|
173
|
+
#
|
174
|
+
# @return [Stream] the elements of +self+ except the last one.
|
175
|
+
def init
|
176
|
+
Stream.lazy {
|
177
|
+
if null?
|
178
|
+
raise List::EmptyError
|
179
|
+
else
|
180
|
+
if tail.null?
|
181
|
+
Stream.null
|
182
|
+
else
|
183
|
+
Stream.cons(->{head}, ->{tail.init})
|
184
|
+
end
|
185
|
+
end
|
186
|
+
}
|
187
|
+
end
|
188
|
+
|
189
|
+
# Creates a string representation of +self+.
|
190
|
+
#
|
191
|
+
# @return [String] a stream representation of +self+.
|
192
|
+
def inspect
|
193
|
+
"Stream[" + inspect_i + "]"
|
194
|
+
end
|
195
|
+
|
196
|
+
def inspect_i(s = nil)
|
197
|
+
if eager?
|
198
|
+
if null?
|
199
|
+
s || ""
|
200
|
+
else
|
201
|
+
h = force.head.eager? ? head.inspect : "?"
|
202
|
+
if s
|
203
|
+
tail.inspect_i(s + ", " + h)
|
204
|
+
else
|
205
|
+
tail.inspect_i(h)
|
206
|
+
end
|
207
|
+
end
|
208
|
+
else
|
209
|
+
if s
|
210
|
+
s + ", ..."
|
211
|
+
else
|
212
|
+
"..."
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
216
|
+
protected :inspect_i
|
217
|
+
|
218
|
+
# Calls +block+ once for each element in +self+.
|
219
|
+
def each(&block)
|
220
|
+
unless null?
|
221
|
+
yield(head)
|
222
|
+
tail.each(&block)
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
# Reduces +self+ using +block+ from right to left. +e+ is used as the
|
227
|
+
# starting value. For example:
|
228
|
+
#
|
229
|
+
# Stream[1, 2, 3].foldr(9) { |x, y| x + y } #=> 1 - (2 - (3 - 9)) = -7
|
230
|
+
#
|
231
|
+
# @param [Object] e the start value.
|
232
|
+
# @return [Object] the reduced value.
|
233
|
+
def foldr(e, &block)
|
234
|
+
if null?
|
235
|
+
e
|
236
|
+
else
|
237
|
+
yield(head, tail.foldr(e, &block))
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
# Reduces +self+ using +block+ from right to left. If +self+ is empty,
|
242
|
+
# +Immutable::List::EmptyError+ is raised.
|
243
|
+
#
|
244
|
+
# @return [Object] the reduced value.
|
245
|
+
def foldr1(&block)
|
246
|
+
if tail.null?
|
247
|
+
head
|
248
|
+
else
|
249
|
+
yield(head, tail.foldr1(&block))
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
# Reduces +self+ using +block+ from left to right. +e+ is used as the
|
254
|
+
# starting value. For example:
|
255
|
+
#
|
256
|
+
# Stream[1, 2, 3].foldl(9) { |x, y| x + y } #=> ((9 - 1) - 2) - 3 = 3
|
257
|
+
#
|
258
|
+
# @param [Object] e the start value.
|
259
|
+
# @return [Object] the reduced value.
|
260
|
+
def foldl(e, &block)
|
261
|
+
if null?
|
262
|
+
e
|
263
|
+
else
|
264
|
+
tail.foldl(yield(e, head), &block)
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
268
|
+
# Reduces +self+ using +block+ from left to right. If +self+ is empty,
|
269
|
+
# +Immutable::List::EmptyError+ is raised.
|
270
|
+
#
|
271
|
+
# @return [Object] the reduced value.
|
272
|
+
def foldl1(&block)
|
273
|
+
tail.foldl(head, &block)
|
274
|
+
end
|
275
|
+
|
276
|
+
# Returns whether +self+ equals to +s+.
|
277
|
+
#
|
278
|
+
# @param [Stream] s the stream to compare.
|
279
|
+
# @return [true, false] +true+ if +self+ equals to +s+; otherwise,
|
280
|
+
# +false+.
|
281
|
+
def ==(s)
|
282
|
+
if !s.is_a?(Stream)
|
283
|
+
false
|
284
|
+
else
|
285
|
+
if null?
|
286
|
+
s.null?
|
287
|
+
else
|
288
|
+
!s.null? && head == s.head && tail == s.tail
|
289
|
+
end
|
290
|
+
end
|
291
|
+
end
|
292
|
+
|
293
|
+
# Appends two streams +self+ and +s+.
|
294
|
+
#
|
295
|
+
# @param [Stream] s the stream to append.
|
296
|
+
# @return [Stream] the new stream.
|
297
|
+
def +(s)
|
298
|
+
Stream.lazy {
|
299
|
+
if null?
|
300
|
+
s
|
301
|
+
else
|
302
|
+
Stream.cons ->{head}, ->{tail + s}
|
303
|
+
end
|
304
|
+
}
|
305
|
+
end
|
306
|
+
|
307
|
+
# Concatenates a stream of streams.
|
308
|
+
#
|
309
|
+
# @return [Stream] the concatenated stream.
|
310
|
+
def flatten
|
311
|
+
Stream.lazy {
|
312
|
+
if null?
|
313
|
+
self
|
314
|
+
else
|
315
|
+
if head.null?
|
316
|
+
tail.flatten
|
317
|
+
else
|
318
|
+
Stream.cons ->{head.head}, ->{
|
319
|
+
Stream.cons(->{head.tail}, ->{tail}).flatten
|
320
|
+
}
|
321
|
+
end
|
322
|
+
end
|
323
|
+
}
|
324
|
+
end
|
325
|
+
|
326
|
+
# Returns the stream obtained by applying the given block to each
|
327
|
+
# element in +self+.
|
328
|
+
#
|
329
|
+
# @return [Stream] the obtained stream.
|
330
|
+
def map(&block)
|
331
|
+
Stream.lazy {
|
332
|
+
if null?
|
333
|
+
Stream.null
|
334
|
+
else
|
335
|
+
Stream.cons ->{ yield(head) }, ->{ tail.map(&block) }
|
336
|
+
end
|
337
|
+
}
|
338
|
+
end
|
339
|
+
|
340
|
+
# Returns the elements of +self+ in reverse order.
|
341
|
+
#
|
342
|
+
# @return [Stream] the reversed stream.
|
343
|
+
def reverse
|
344
|
+
foldl(Stream.null) { |x, y| Stream.cons(->{y}, ->{x}) }
|
345
|
+
end
|
346
|
+
|
347
|
+
# Returns a new stream obtained by inserting +sep+ in between the
|
348
|
+
# elements of +self+.
|
349
|
+
#
|
350
|
+
# @param [Object] sep the object to insert between elements.
|
351
|
+
# @return [Stream] the new stream.
|
352
|
+
def intersperse(sep)
|
353
|
+
Stream.lazy {
|
354
|
+
if null?
|
355
|
+
self
|
356
|
+
else
|
357
|
+
Stream.cons(->{head}, ->{tail.prepend_to_all(sep)})
|
358
|
+
end
|
359
|
+
}
|
360
|
+
end
|
361
|
+
|
362
|
+
def prepend_to_all(sep)
|
363
|
+
Stream.lazy {
|
364
|
+
if null?
|
365
|
+
self
|
366
|
+
else
|
367
|
+
Stream.cons ->{sep}, ->{
|
368
|
+
Stream.cons ->{head}, ->{tail.prepend_to_all(sep)}
|
369
|
+
}
|
370
|
+
end
|
371
|
+
}
|
372
|
+
end
|
373
|
+
protected :prepend_to_all
|
374
|
+
|
375
|
+
# Returns a new stream obtained by inserting +xs+ in between the streams
|
376
|
+
# in +self+ and concatenates the result.
|
377
|
+
# +xss.intercalate(xs)+ is equivalent to
|
378
|
+
# +xss.intersperse(xs).flatten+.
|
379
|
+
#
|
380
|
+
# @param [Stream] xs the stream to insert between streams.
|
381
|
+
# @return [Stream] the new stream.
|
382
|
+
def intercalate(xs)
|
383
|
+
intersperse(xs).flatten
|
384
|
+
end
|
385
|
+
|
386
|
+
# Returns the first element in +self+ for which the given block
|
387
|
+
# evaluates to true. If such an element is not found, it
|
388
|
+
# returns +nil+.
|
389
|
+
#
|
390
|
+
# @return [Object] the found element.
|
391
|
+
def find(&block)
|
392
|
+
if null?
|
393
|
+
nil
|
394
|
+
else
|
395
|
+
if yield(head)
|
396
|
+
head
|
397
|
+
else
|
398
|
+
tail.find(&block)
|
399
|
+
end
|
400
|
+
end
|
401
|
+
end
|
402
|
+
|
403
|
+
# Returns the elements in +self+ for which the given block evaluates to
|
404
|
+
# true.
|
405
|
+
#
|
406
|
+
# @return [Stream] the elements that satisfies the condition.
|
407
|
+
def filter(&block)
|
408
|
+
Stream.lazy {
|
409
|
+
if null?
|
410
|
+
Stream.null
|
411
|
+
else
|
412
|
+
if yield(head)
|
413
|
+
Stream.cons ->{ head }, ->{ tail.filter(&block) }
|
414
|
+
else
|
415
|
+
tail.filter(&block)
|
416
|
+
end
|
417
|
+
end
|
418
|
+
}
|
419
|
+
end
|
420
|
+
|
421
|
+
# Returns the +n+th element of +self+. If +n+ is out of range, +nil+ is
|
422
|
+
# returned.
|
423
|
+
#
|
424
|
+
# @return [Object] the +n+th element.
|
425
|
+
def [](n)
|
426
|
+
if n < 0 || null?
|
427
|
+
nil
|
428
|
+
elsif n == 0
|
429
|
+
head
|
430
|
+
else
|
431
|
+
tail[n - 1]
|
432
|
+
end
|
433
|
+
end
|
434
|
+
|
435
|
+
# Returns the first +n+ elements of +self+, or +self+ itself if
|
436
|
+
# +n > self.length+.
|
437
|
+
#
|
438
|
+
# @param [Integer] n the number of elements to take.
|
439
|
+
# @return [Stream] the first +n+ elements of +self+.
|
440
|
+
def take(n)
|
441
|
+
Stream.lazy {
|
442
|
+
if n <= 0 || null?
|
443
|
+
Stream.null
|
444
|
+
else
|
445
|
+
Stream.cons ->{ head }, ->{ tail.take(n - 1) }
|
446
|
+
end
|
447
|
+
}
|
448
|
+
end
|
449
|
+
|
450
|
+
# Returns the suffix of +self+ after the first +n+ elements, or
|
451
|
+
# +Stream[]+ if +n > self.length+.
|
452
|
+
#
|
453
|
+
# @param [Integer] n the number of elements to drop.
|
454
|
+
# @return [Stream] the suffix of +self+ after the first +n+ elements.
|
455
|
+
def drop(n)
|
456
|
+
Stream.lazy {
|
457
|
+
if n <= 0 || null?
|
458
|
+
self
|
459
|
+
else
|
460
|
+
tail.drop(n - 1)
|
461
|
+
end
|
462
|
+
}
|
463
|
+
end
|
464
|
+
|
465
|
+
# Returns the longest prefix of the elements of +self+ for which +block+
|
466
|
+
# evaluates to true.
|
467
|
+
#
|
468
|
+
# @return [Stream] the prefix of the elements of +self+.
|
469
|
+
def take_while(&block)
|
470
|
+
Stream.lazy {
|
471
|
+
if null? || !yield(head)
|
472
|
+
Stream.null
|
473
|
+
else
|
474
|
+
Stream.cons ->{ head }, ->{ tail.take_while(&block) }
|
475
|
+
end
|
476
|
+
}
|
477
|
+
end
|
478
|
+
|
479
|
+
# Returns the suffix remaining after
|
480
|
+
# +self.take_while(&block)+.
|
481
|
+
#
|
482
|
+
# @return [Stream] the suffix of the elements of +self+.
|
483
|
+
def drop_while(&block)
|
484
|
+
Stream.lazy {
|
485
|
+
if null? || !yield(head)
|
486
|
+
self
|
487
|
+
else
|
488
|
+
tail.drop_while(&block)
|
489
|
+
end
|
490
|
+
}
|
491
|
+
end
|
492
|
+
|
493
|
+
# Converts +self+ to a list.
|
494
|
+
#
|
495
|
+
# @return [List] a list.
|
496
|
+
def to_list
|
497
|
+
foldr(List[]) { |x, xs| Cons[x, xs] }
|
498
|
+
end
|
499
|
+
|
500
|
+
# Builds a stream from the seed value +e+ and the given block. The block
|
501
|
+
# takes a seed value and returns +nil+ if the seed should
|
502
|
+
# unfold to the empty stream, or returns +[a, b]+, where
|
503
|
+
# +a+ is the head of the stream and +b+ is the
|
504
|
+
# next seed from which to unfold the tail. For example:
|
505
|
+
#
|
506
|
+
# xs = List.unfoldr(3) { |x| x == 0 ? nil : [x, x - 1] }
|
507
|
+
# p xs #=> List[3, 2, 1]
|
508
|
+
#
|
509
|
+
# +unfoldr+ is the dual of +foldr+.
|
510
|
+
#
|
511
|
+
# @param [Object] e the seed value.
|
512
|
+
# @return [Stream] the stream built from the seed value and the block.
|
513
|
+
def self.unfoldr(e, &block)
|
514
|
+
Stream.lazy {
|
515
|
+
x = yield(e)
|
516
|
+
if x.nil?
|
517
|
+
Stream.null
|
518
|
+
else
|
519
|
+
y, z = x
|
520
|
+
Stream.cons ->{ y }, ->{ unfoldr(z, &block) }
|
521
|
+
end
|
522
|
+
}
|
523
|
+
end
|
524
|
+
|
525
|
+
# Takes zero or more streams and returns a new stream in which each
|
526
|
+
# element is an array of the corresponding elements of +self+ and the
|
527
|
+
# input streams.
|
528
|
+
#
|
529
|
+
# @param [Array<Stream>] xss the input streams.
|
530
|
+
# @return [Stream] the new stream.
|
531
|
+
def zip(*xss)
|
532
|
+
Stream.lazy {
|
533
|
+
if null?
|
534
|
+
self
|
535
|
+
else
|
536
|
+
heads = xss.map { |xs| xs.null? ? nil : xs.head }
|
537
|
+
tails = xss.map { |xs| xs.null? ? Stream.null : xs.tail }
|
538
|
+
Stream.cons ->{ [head, *heads] }, ->{ tail.zip(*tails) }
|
539
|
+
end
|
540
|
+
}
|
541
|
+
end
|
542
|
+
|
543
|
+
# Takes zero or more streams and returns the stream obtained by applying
|
544
|
+
# the given block to an array of the corresponding elements of +self+
|
545
|
+
# and the input streams.
|
546
|
+
# +xs.zip_with(*yss, &block)+ is equivalent to
|
547
|
+
# +xs.zip(*yss).map(&block)+.
|
548
|
+
#
|
549
|
+
# @param [Array<Stream>] xss the input streams.
|
550
|
+
# @return [Stream] the new stream.
|
551
|
+
def zip_with(*xss, &block)
|
552
|
+
Stream.lazy {
|
553
|
+
if null?
|
554
|
+
self
|
555
|
+
else
|
556
|
+
heads = xss.map { |xs| xs.null? ? nil : xs.head }
|
557
|
+
tails = xss.map { |xs| xs.null? ? Stream.null : xs.tail }
|
558
|
+
h = yield(head, *heads)
|
559
|
+
Stream.cons ->{ h }, ->{ tail.zip_with(*tails, &block) }
|
560
|
+
end
|
561
|
+
}
|
562
|
+
end
|
563
|
+
end
|
564
|
+
end
|