1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
# ............................................................................................... #
#
# Copyright (c) 2012-2018 Institut National des Sciences Appliquées de Lyon (INSA Lyon) and others
#
# This program and the accompanying materials are made available under the
# terms of the Eclipse Public License 2.0 which is available at
# http://www.eclipse.org/legal/epl-2.0.
#
# SPDX-License-Identifier: EPL-2.0
#
# ............................................................................................... #
----
This module defines utility functions and augmentations to ease the use of
the `gololang.LazyList` object.
A lazy list is an *immutable* list whose elements are evaluated only when needed,
as can be found in Haskell for example.
This is very useful when using higher order function such as `map`. Mapping
a long lazy list with a function and using only the 3 first elements will only
apply the function to these elements, as opposed to regular lists.
Lazy lists can also be used to create infinite lists, also known as generators
(or anamorphisms).
Lastly, they allow for elegant recursive implementations of several classical
algorithms.
For instance, one can create a infinite lazy list containing integers as:
function count = |start| -> cons(start, -> count(start + 1_L))
function count = -> count(0_L)
On the other hand, functions or methods like `equals`, `size` or `contains` are
not very efficients, since they must evaluate the whole list, and thus negate
the laziness. They are here for completeness and compatibility with the regular
lists interface, but you should avoid such methods.
Some functions in this module are recursive (re)implementation of standard list
HOF, such as `map` or `filter`. The recursive aspect should not be limiting since
the resulting list is lazy.
----
module gololang.LazyLists
import java.util
# ............................................................................................... #
# Utils, constructors and conversions
----
Returns the empty list.
----
function emptyList = -> gololang.LazyList.EMPTY()
----
Create a new lazy list from a head and tail values. Automatically wraps the
tail in a closure if its not already one.
For example:
let myList = cons(1, cons(2, cons(3, emptyList())))
gives a lazy list equivalent to `list[1, 2, 3]`
----
function cons = |head, tail| -> match {
when isClosure(tail) then gololang.LazyList.cons(head, tail)
when tail is null then gololang.LazyList.cons(head, ^emptyList)
otherwise gololang.LazyList.cons(head, -> tail)
}
----
Unary version of [`cons(head, tail)`](#cons_2).
Its parameter is assumed to be a tuple (or any object having a `get(idx)` method)
of the form `[head, tail]`.
----
function cons = |ht| -> cons(ht: get(0), ht: get(1))
----
Variadic function to create lazy lists from values.
let myList = lazyList(1, 2, 3, 4)
is the equivalent to
let myList = cons(1, cons(2, cons(3, cons(4, emptyList()))))
----
function lazyList = |values...| -> iteratorToLazyList(values: asList(): iterator())
----
Wraps any object implementing `Iterable` or `Iterator` in a lazy list.
The `next()` method of the underlying iterator is only called when the tail is
used.
NOTE:
If called with an `Iterator` instance, the iterator is shared, so navigating
through the list can have side effects if another object is using the same
iterator.
----
function fromIter = |it| -> match {
when it oftype Iterable.class then
iteratorToLazyList(it: iterator())
when it oftype Iterator.class then
iteratorToLazyList(it)
otherwise raise("Invalid argument for fromIter")
}
augment java.lang.Iterable {
----
Returns a lazy list from this `Iterable`. Can be used for instance to lazily
map a list.
----
function asLazyList = |this| -> iteratorToLazyList(this: iterator())
}
augment java.util.Iterator {
----
Returns a lazy list view of this `Iterator`.
----
function asLazyList = |this| -> iteratorToLazyList(this)
}
local function iteratorToLazyList = |iterator| {
if not iterator: hasNext() {
return gololang.LazyList.EMPTY()
} else {
let head = iterator: next()
return gololang.LazyList.cons(head, -> iteratorToLazyList(iterator))
}
}
# ............................................................................................... #
augment gololang.LazyList {
----
Maps elements of a list using a function:
lazyList(1, 2, 3):map(|x| -> 2 * x)
`map` returns a new lazy list, i.e. `func` is applied only
when necessary.
This is a recursive implementation.
----
function map = |this, func| -> match {
when this: isEmpty() then gololang.LazyList.EMPTY()
otherwise gololang.LazyList.cons(
func(this: head()), -> this: tail(): map(func)
)
}
----
Filters elements based on a predicate.
Returns a new lazy list.
----
function filter = |this, pred| -> match {
when this: isEmpty() then gololang.LazyList.EMPTY()
when pred(this: head()) then
gololang.LazyList.cons(this: head(), -> this: tail(): filter(pred))
otherwise this: tail(): filter(pred)
}
----
Finds the first element of a list matching a predicate:
println(lazyList(1, 2, 3, 4): find(|n| -> n > 3))
* `this`: a lazy list.
* `pred`: a predicate function taking an element and returning a boolean.
`find` returns `null` when no element satisfies `pred`.
Note that in the worst case, all the list is search. Take care to **not use**
this method on infinite list, since no check is made.
----
function find = |this, pred| -> match {
when this: isEmpty() then null
when pred(this: head()) then this: head()
otherwise this: tail(): find(pred)
}
----
Joins the elements into a string:
println(list[1, 2, 3]: join(", "))
* `this`: a list.
* `separator`: the element separator string.
The returned string is `""` when the list is empty.
----
function join = |this, separator| {
if this: isEmpty() {
return ""
}
var it = this: iterator()
var buffer = java.lang.StringBuilder(it: next(): toString())
while it: hasNext() {
buffer: append(separator)
buffer: append(it: next())
}
return buffer: toString()
}
----
Folds left `this` using `func` with `zero` as initial value.
This is a recursive implementation.
lazyList(a, b, c): foldl(f, z) == f(f(f(z, a), b), c)
Equivalent to `foldr` if `func` is commutative.
----
function foldl = |this, func, zero| -> match {
when this: isEmpty() then zero
otherwise this: tail(): foldl(func, func(zero, this: head()))
}
----
Folds right `this` using `func` with `zero` as initial value.
This is a recursive implementation.
lazyList(a, b, c): foldr(f, z) == f(a, f(b, f(c, z)))
Equivalent to `foldl` if `func` is commutative.
----
function foldr = |this, func, zero| -> match {
when this: isEmpty() then zero
otherwise func(this: head(), this: tail(): foldr(func, zero))
}
----
Takes the `nb` first elements of the lazy list, as a lazy list.
This is a wrapper, the underlying list is resolved on demand, such that
everything remains lazy. `take` can thus be used on infinite lists.
----
function take = |this, nb| -> match {
when nb <= 0 or this: isEmpty() then emptyList()
otherwise gololang.LazyList.cons(
this: head(),
-> this: tail(): take(nb - 1)
)
}
----
Takes elements from the list as long as the given predicate is true.
* `pred`: the predicate function used to end the list.
----
function takeWhile = |this, pred| -> match {
when this: isEmpty() or not pred(this: head()) then emptyList()
otherwise gololang.LazyList.cons(this: head(), -> this: tail() :takeWhile(pred))
}
----
Remove `nb` elements from the list and return the rest as a lazy list.
----
function drop = |this, nb| -> match {
when nb <= 0 then this
when this: isEmpty() then emptyList()
otherwise this: tail(): drop(nb - 1)
}
----
Remove elements from the list as long as the given predicate is true.
* `pred`: the predicate function used to end the list.
----
function dropWhile = |this, pred| -> match {
when this: isEmpty() then emptyList()
when not pred(this: head()) then this
otherwise this: tail(): dropWhile(pred)
}
----
Extract a lazy sublist.
This is just a convenient method for `list: drop(from):take(to - from)`, so
the list remains lazy.
* `from`: low endpoint (inclusive) of the `subList`
* `to`: high endpoint (exclusive) of the `subList`
----
function subList = |this, from, to| -> this:drop(from):take(to - from)
}
----
Generator function on lazy lists (anamorphism).
This function generates a (possibly infinite) lazy list. Starting with the
`seed` value, if `finished(seed)` is `true`, the generation stops and an empty
list is returned. Otherwise, `unspool` is called on `seed`, and must generate
two values: the head of the list (current value) and the next seed that will be
used to generate the tail.
As an example, one can write a simple `range` function as:
let range = |start, end| -> generator(
|seed| -> [seed, seed + 1],
|seed| -> seed >= end,
start
)
* `unspool`: the generative function
* `finished`: the condition function
* `seed`: the initial value
----
function generator = |unspool, finished, seed| {
if finished(seed) {
return gololang.LazyList.EMPTY()
}
let r = unspool(seed)
return gololang.LazyList.cons(
r:get(0),
-> generator(unspool, finished, r:get(1))
)
}
local function False = |args...| -> false
----
Produces a infinite list of values. If the argument is a closure, it must have
no parameters, and it's used to produce the values (called for each `tail`
access).
For instance, `repeat(5)` will return an infinite lazy list of `5`s, and
`repeat(^f)` will return a infinite lazy list of calls to `f`
([f(), f(), f(), ...])
* `value`: a value or a closure
----
function repeat = |value| -> match {
when isClosure(value) then generator(|seed| -> [value(), null], ^False, null)
otherwise generator(|seed| -> [value, null], ^False, null)
}
----
Returns an infinite lazy list produced by iterative application of a function
to an initial element.
`iterate(z, f)` thus yields `z, f(z), f(f(z)), ...`
For instance, one can create a infinite list of integers using:
iterate(0, |x| -> x + 1)
* `zero`: the initial element of the list
* `func`: the function to apply
----
function iterate = |zero, func| -> generator(|seed| -> [seed, func(seed)], ^False, zero)