Skip to content

Commit a3e32b9

Browse files
authored
Streams (#266)
This commit introduces a new Stream module for streams (also known as lazy lists) to the standard library.
1 parent b3a22f8 commit a3e32b9

File tree

3 files changed

+286
-5
lines changed

3 files changed

+286
-5
lines changed

lib/Lazy.fram

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,10 @@ data RefP = RefP of {Ref : type -> type}
2121

2222
let RefP {Ref} = (extern dbl_abstrType : Unit ->[IO] RefP) ()
2323

24-
let ref {type X} = (extern dbl_ref : X ->[] Ref X)
25-
method get {type X} = (extern dbl_refGet : Ref X ->[] X)
26-
method set {type X} = (extern dbl_refSet : Ref X -> X ->[] Unit)
24+
let ref {type X} = (extern dbl_ref : X ->[] Ref X)
25+
let pureRef {type X} = (extern dbl_ref : X -> Ref X)
26+
method get {type X} = (extern dbl_refGet : Ref X ->[] X)
27+
method set {type X} = (extern dbl_refSet : Ref X -> X ->[] Unit)
2728

2829
# Internal representation of lazy state
2930
data LazyState A =
@@ -37,8 +38,12 @@ abstr data Lazy X = Lazy of Ref (LazyState X)
3738
{## Creates new suspension from a given computation. ##}
3839
pub let lazy {type X} (f : Unit ->[] X) = Lazy (ref (Thunk f))
3940

40-
{## Initializes a lazy with a value directly. ##}
41-
pub let pureLazy {type X} (v : X) = Lazy (ref (Done v))
41+
{##
42+
Creates a lazy value containing a fully evaluated result.
43+
This is equivalent to constructing an immutable value directly,
44+
so this function is pure.
45+
##}
46+
pub let pureLazy {type X} (v : X) = Lazy (pureRef (Done v))
4247

4348
{##
4449
Forces the evaluation of a suspension.

lib/Stream.fram

Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
{# This file is part of DBL, released under the MIT license.
2+
# See LICENSE for details.
3+
#}
4+
5+
import open /Lazy
6+
7+
## # Stream module
8+
9+
{##
10+
This module provides lazy lists, also known as streams.
11+
Each and every node of a stream is deferred, meaning that
12+
no computation occurs unless results are forcibly read.
13+
14+
Contrary to regular lists, streams may be infinite. Some iterative
15+
functions may never terminate!
16+
##}
17+
18+
rec
19+
## Stream type.
20+
abstr data Stream X = Stream of Lazy (Node X)
21+
data Node X =
22+
| Cons of X, Stream X
23+
| Nil
24+
end
25+
26+
parameter X : type
27+
28+
method unstream (Stream xs) = xs
29+
30+
{## Creates stream from given list. ##}
31+
pub let rec fromList (xs : List X) =
32+
Stream (lazy (fn _ =>
33+
match xs with
34+
| [] => Nil
35+
| x :: xs => Cons x (fromList xs)
36+
end)) : Stream X
37+
38+
{## Returns all elements of a stream as a list. ##}
39+
pub let rec toList (Stream xs : Stream X) =
40+
match xs.force with
41+
| Nil => [] : List X
42+
| Cons x xs => x :: toList xs
43+
end : List X
44+
45+
{##
46+
Initializes lazy stream by iteratively applying function `f` on previous results.
47+
48+
This function may generate infinite stream.
49+
##}
50+
pub let rec unfold {S} (seed : S) (f : S ->[] Option (Pair X S)) =
51+
Stream (lazy (fn _ =>
52+
match f seed with
53+
| None => Nil
54+
| Some (val, seed) => Cons val (unfold seed f)
55+
end)) : Stream X
56+
57+
{## Creates an empty stream. ##}
58+
pub let empty = (Stream (pureLazy Nil) : Stream X)
59+
60+
{## Checks if a stream is empty. ##}
61+
pub let isEmpty (Stream xs : Stream X) =
62+
match xs.force with
63+
| Nil => True
64+
| _ => False
65+
end
66+
67+
{## Creates a stream from given element. ##}
68+
pub let singleton (elem : X) =
69+
Stream (pureLazy (Cons elem empty))
70+
71+
{## Adds an element to the head of a stream. ##}
72+
pub let cons (elem : X) (tail : Stream X) =
73+
Stream (pureLazy (Cons elem tail))
74+
75+
{## Adds a deferred element to the head of a stream. ##}
76+
pub let lazyCons (elem : Lazy X) (tail : Stream X) =
77+
Stream (lazy (fn _ => Cons elem.force tail))
78+
79+
{##
80+
Returns head and tail of a stream.
81+
Returns `None` if stream is empty.
82+
##}
83+
pub let uncons (Stream xs : Stream X) =
84+
match xs.force with
85+
| Nil => None
86+
| Cons x xs => Some (x, xs)
87+
end
88+
89+
{##
90+
Creates a new stream with mapped values.
91+
##}
92+
pub let rec map {Y} (f : X ->> Y) (Stream xs : Stream X) =
93+
Stream (lazy (fn _ =>
94+
match xs.force with
95+
| Nil => Nil
96+
| Cons x xs => Cons (f x) (map f xs)
97+
end)) : Stream Y
98+
99+
{## Appends two streams together. ##}
100+
pub let rec append (Stream xs : Stream X) (ys : Stream X) =
101+
Stream (lazy (fn _ =>
102+
match xs.force with
103+
| Nil => ys.unstream.force
104+
| Cons x xs => Cons x (append xs ys)
105+
end)) : Stream X
106+
107+
{## Performs monadic bind over a stream. ##}
108+
pub let rec concatMap {Y} (f : X ->> Stream Y) (Stream xs : Stream X) =
109+
Stream (lazy (fn _ =>
110+
match xs.force with
111+
| Nil => Nil
112+
| Cons x xs => append (f x) (concatMap f xs) >.unstream >.force
113+
end)) : Stream Y
114+
115+
{##
116+
Returns the longest prefix of a stream that satisfies the given predicate.
117+
##}
118+
pub let rec takeWhile (f : X ->> Bool) (Stream xs : Stream X) =
119+
Stream (lazy (fn _ =>
120+
match xs.force with
121+
| Nil => Nil
122+
| Cons x xs =>
123+
if f x then
124+
Cons x (takeWhile f xs)
125+
else
126+
Nil
127+
end)) : Stream X
128+
129+
{## Returns a substream with values that satisfy the given predicate. ##}
130+
pub let rec filter (f : X ->> Bool) (Stream xs : Stream X) =
131+
Stream (lazy (fn _ =>
132+
match xs.force with
133+
| Nil => Nil
134+
| Cons x xs =>
135+
if f x then
136+
Cons x (filter f xs)
137+
else
138+
filter f xs >.unstream >.force
139+
end)) : Stream X
140+
141+
{##
142+
Folds stream to a single value, beginning with right-most value.
143+
##}
144+
pub let rec foldRight {A}
145+
(f : X -> A ->> A) (Stream xs : Stream X) (acc : A) =
146+
match xs.force with
147+
| Nil => acc
148+
| Cons x xs => f x (foldRight f xs acc)
149+
end : A
150+
151+
{##
152+
Folds stream to a single value from right to left. Takes the last
153+
element of a stream as an initial accumulator.
154+
Calls `~onError` in case of an empty stream.
155+
156+
@param ~onError Fallback for an empty stream.
157+
##}
158+
pub let foldRight1Err {~onError : Unit ->> X}
159+
(f : X -> X ->> X) (Stream xs : Stream X) =
160+
let rec foldRight1ErrAux y (Stream xs) =
161+
match xs.force with
162+
| Nil => y
163+
| Cons x xs =>
164+
f y (foldRight1ErrAux x xs)
165+
end
166+
in
167+
match xs.force with
168+
| Nil => ~onError ()
169+
| Cons x xs => foldRight1ErrAux x xs
170+
end : X
171+
172+
{## Checks if all elements of given streams are equal pairwise. ##}
173+
pub let equal
174+
{method equal : X -> X ->[] Bool}
175+
(xs : Stream X)
176+
(ys : Stream X) =
177+
let rec equalAux ((Stream xs) : Stream X) ((Stream ys) : Stream X) =
178+
match (xs.force, ys.force) with
179+
| Nil, Nil => True
180+
| Cons x xs, Cons y ys =>
181+
if x == y then
182+
equalAux (xs : Stream X) ys
183+
else
184+
False
185+
| _ => False
186+
end
187+
in
188+
equalAux xs ys
189+
190+
{## Enables stream showing in REPL. ##}
191+
pub let show (_ : Stream X) = "#Stream"
192+
193+
parameter ~onError
194+
195+
pub method toList = toList
196+
pub method uncons = uncons
197+
pub method map = flip map
198+
pub method add = append
199+
pub method concatMap = flip concatMap
200+
pub method filter = flip filter
201+
pub method foldRight xs f acc = foldRight f xs acc
202+
pub method foldRight1Err = flip foldRight1Err
203+
pub method equal = equal
204+
pub method show = show

test/stdlib/stdlib0007_Stream.fram

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import List
2+
import Stream
3+
4+
let _ = assert {msg="fromList > toList"}
5+
(let xs = [1, 2, 3, 4] in
6+
xs == Stream.toList (Stream.fromList xs))
7+
8+
let _ = assert {msg="toList > fromList"}
9+
(let xs = Stream.cons 10 (Stream.cons 20 (Stream.empty)) in
10+
xs == Stream.fromList (Stream.toList xs))
11+
12+
let _ =
13+
let f x = if 2 == x then None else Some (x, 1 + x) in
14+
assert {msg="unfold"}
15+
(Stream.fromList [0, 1] == Stream.unfold 0 f)
16+
17+
let _ = assert {msg="isEmpty"} (Stream.isEmpty (Stream.empty {X=Int}))
18+
let _ = assert {msg="isEmpty"} (not (Stream.isEmpty (Stream.fromList [1])))
19+
20+
let _ = assert {msg="singleton"} (Stream.singleton 1 == Stream.fromList [1])
21+
22+
let _ = assert {msg="cons"}
23+
(Stream.cons 3 (Stream.fromList [2, 1]) == Stream.fromList [3, 2, 1])
24+
25+
let _ = assert {msg="uncons"}
26+
match Stream.uncons (Stream.fromList [1, 2, 3]) with
27+
| Some (x, _) => 1 == x
28+
| None => False
29+
end
30+
let _ = assert {msg="uncons"}
31+
match Stream.uncons (Stream.empty {X=Int}) with
32+
| None => True
33+
| _ => False
34+
end
35+
36+
let _ = assert {msg="map"}
37+
(Stream.fromList ["1", "2"]
38+
== Stream.map (fn x => (x : Int).toString) (Stream.fromList [1, 2]))
39+
40+
let _ = assert {msg="append"}
41+
(Stream.append (Stream.fromList [1, 2, 3]) (Stream.fromList [4, 5, 6])
42+
== Stream.fromList [1, 2, 3, 4, 5, 6])
43+
44+
let _ = assert {msg="concatMap"}
45+
(Stream.fromList [1, 2, 3] >.concatMap (fn (x : Int) => Stream.fromList [x + 1, x - 1])
46+
== Stream.fromList [2, 0, 3, 1, 4, 2])
47+
48+
let _ = assert {msg="takeWhile"}
49+
(Stream.takeWhile (fn (x : Int) => x <= 2) (Stream.fromList [1, 2, 3, 4, 1])
50+
== Stream.fromList [1, 2])
51+
52+
let _ = assert {msg="filter"}
53+
(Stream.filter (fn (x : Int) => x <= 2) (Stream.fromList [1, 2, 3, 4, 1])
54+
== Stream.fromList [1, 2, 1])
55+
56+
let _ = assert {msg="foldRight"}
57+
(Stream.foldRight (fn (x : Int) y => x + y) (Stream.fromList [1, 2, 3, 4]) 0
58+
== 10)
59+
60+
let _ = assert {msg="foldRight1Err"}
61+
(Stream.foldRight1Err {~onError = fn _ => 42}
62+
(fn (x : Int) y => x + y) (Stream.fromList [1, 2, 3, 4])
63+
== 10)
64+
let _ = assert {msg="foldRight1Err"}
65+
(Stream.foldRight1Err {~onError = fn _ => 42}
66+
(fn (x : Int) y => x + y) (Stream.empty {X=Int})
67+
== 42)
68+
69+
let _ = assert {msg="equal"}
70+
(Stream.equal (Stream.fromList [1, 2, 3]) (Stream.fromList [1, 2, 3]))
71+
let _ = assert {msg="equal"}
72+
(not (Stream.equal (Stream.fromList [1, 2, 3]) (Stream.fromList [4, 5, 6])))

0 commit comments

Comments
 (0)