Add missing primitive implementation for the plain interpreter.
"Alternative 1: relies on pattern-matching within objects (the [.first;.rest] bit)"
define stream:cons(f,r) = [.first = f; .rest = r] ^ StreamOperations;
define stream:nil = [] ^ StreamOperations;
StreamOperations = [
.foldl acc f -> rec loop(acc, self) {acc, x@[.first;.rest] -> loop(f(x.first, acc), x.rest);
acc, _ -> acc};
.foldr acc f -> rec loop(self) {x@[.first;.rest] -> f(x.first, loop(x.rest));
_ -> acc};
.do f -> rec loop(self) {x@[.first;.rest] -> do f(x.first); loop(x.rest); _ -> .ok};
.map f -> self.foldr stream:nil {v, acc -> stream:cons(f v, acc)};
.filter pred -> self.foldr stream:nil {v, acc ->
if (pred v) { stream:cons(v, acc) } else { acc } };
];
define stream:range = {
high -> stream:range(0, high);
low, high -> stream:range(low, high, 1);
low, high, step ->
if (low >= high) {
stream:nil
} else {
stream:cons(low, stream:range(low + step, high, step))
}
};
do someStream.map { x -> x + 1 };
"---------------------------------------------------------------------------"
"Alternative 2: uses reified-message algebraic-type-modelling pattern"
"(note: .foo visitor is defined to evaluate to visitor .foo)"
define # tag val = {visitor -> visitor tag val};
define stream:cons(f,r) = StreamOperations / #.next(f, r);
define stream:nil = StreamOperations / .empty;
StreamOperations = [
.foldl acc fn -> rec loop(acc, self) {acc, x -> x {
.empty = acc;
.next(f, r) -> loop(fn(f, acc), r);
}};
.foldr acc fn -> rec loop(self) {x -> x {
.empty = acc;
.next(f, r) -> fn(f, loop(r));
}};
.do fn -> rec loop(self) {x -> x {
.empty = .ok;
.next(f, r) -> do fn(f); loop(r);
}};
];
"---------------------------------------------------------------------------"
"Alternative 3: extends alternative 2 with special pattern-matching extensions
for supporting visitors."
namespace s = 'http://eighty-twenty.org/ns/stream' in
define # tag val = {visitor -> visitor tag val};
define s:cons(f,r) = s:StreamOperations / #.s:next(f, r);
define s:nil = s:StreamOperations / .s:empty;
define binop selector = {left, right -> left selector right};
s:StreamOperations = [
.s:foldl acc fn -> rec loop(acc, self) {acc, {.s:empty} -> acc;
acc, {.s:next(f, r)} -> loop(fn(f, acc), r)};
.s:foldr acc fn -> rec loop(self) {{.s:empty} = acc;
{.s:next(f, r)} -> fn(f, loop(r))};
.s:do fn -> rec loop(self) {{.s:empty} = .:ok;
{.s:next(f, r)} -> do fn(f); loop(r)};
.s:inject -> self.s:foldl;
];
[1, 2, 3].s:do{each -> println each};
[1, 2, 3].s:inject 0 {acc, each -> acc + each}; "6"
[1, 2, 3].s:inject 0 (binop +) "6"
[1, 2, 3].s:map (1 +) "[2, 3, 4]"
[1, 2, 3].s:map .:negated "[-1, -2, -3]"
"[a, b, c] is shorthand for stream:cons(a, stream:cons(b, stream:cons(c, stream:nil)))"
"[a, b | c] is shorthand for stream:cons(a, stream:cons(b, c))"
"[| c] is thus 'shorthand' (longhand??) for c"
"[a, b, c | ] is equivalent to [a, b, c]"
"thus [|] is the empty stream, stream:nil"
"When used in patterns, these shorthands are expanded into appropriate visitors."
"Reserving '|' in this way gives us a nice hook for stream comprehensions later on."
[ x || x <- [1, 2, 3, 4], x.number:isEven ]
s:StreamOperations = [
.s:foldl acc fn -> rec loop(acc, self) {acc, [|] -> acc;
acc, [f|r] -> loop(fn(f, acc), r)};
.s:foldr acc fn -> rec loop(self) {[|] = acc;
[f|r] -> fn(f, loop(r))};
.s:do fn -> rec loop(self) {[|] = .:ok;
[f|r] -> do fn(f); loop(r)};
.s:inject -> self.s:foldl;
];
"How about extending the algebraic datatype idea to the family of tuples?"
define (a,b) v = v 2 a b
define (a,b,c) v = v 3 a b c
"etc. The arity is required to permit pattern-matching based on it, as
pattern-matching by arity doesn't work with curried functions."
"---------------------------------------------------------------------------"
"Alternative 4: first-class messages, in <angle brackets>. Not quite
objects, because sends (message object) reduce to (object message),
and obviously (object object) reduces normally, but (message message)
is an error."
"Here, .foo has no protocol at all, not even the implicit visitor
protocol (.foo bar) --> (bar .foo) used earlier. Atoms are thus
totally inert - they're just simple tags with no behaviour. Messages,
on the other hand, have the meaning <x y z> --> {r -> r x y z} except
that the x,y,z is evaluated before being closed over - so more like
<x y z> --> let (x1,y1,z1) = (x,y,z) in {r -> r x1 y1 z1}."
"Hey, that's neat - <> is the identity object!"
"and <+ 2> is different from (2 +)..."
"Using <> in a pattern causes a visitor-probe to be sent to the object
being matched. Obviously, it has to be expecting such a probe, so
data objects (ie algebraic data type instances, as opposed to
functions or other kinds of receiver) should be coded with
visitor-DNU-behaviour,
{...; visitor -> visitor .thisIsWhatIam (someArg, otherArg)}
Note that the sends to the visitor must occur in tail position. (What
happens if they're not?)
A visitor-probe for the pattern <.foo bar> looks like
{ .foo bar -> ...continuation of matching...;
_ -> ...backtrack to next clause... }
So the case below, {acc, <.s:empty> -> acc;
acc, <.s:next(f, r)> -> loop(fn(f, acc), r)},
'expands' to a match tree like
- match tuple
- length 2
- element 0: accept any, bind to 'acc'
- element 1: visitor-probe {
.s:empty -> run continuation 'acc'
.s:next -> - match tuple
- length 2
- element 0: ...
"
"What happens when there are two visitors in a tuple?
{<.s:empty>, <.s:empty> -> .bothEmpty;
_, <.s:empty> -> .secondEmpty;
<.s:empty>, _ -> .firstEmpty;
_, _ -> .neitherEmpty}
It basically needs to backtrack. It should sort the branches
appropriately before building the match tree:
{<.s:empty>, <.s:empty> -> .bothEmpty;
<.s:empty>, _ -> .firstEmpty;
_, <.s:empty> -> .secondEmpty;
_, _ -> .neitherEmpty}
(and I'm thinking of the more general case here, where you might fail
to match in the second element of the tuple, necessitating a
backtrack out of the match in the first element. Something like {(1,
2) -> .a; (_, _) -> .b} when presented with (1, 3).)"
namespace s = 'http://eighty-twenty.org/ns/stream' in
define s:cons(f,r) = s:StreamOperations / <.s:next(f, r)>;
define s:nil = s:StreamOperations / <.s:empty>;
s:StreamOperations = [
.s:foldl acc fn -> rec loop(acc, self) {acc, <.s:empty> -> acc;
acc, <.s:next(f, r)> -> loop(fn(f, acc), r)};
.s:foldr acc fn -> rec loop(self) {<.s:empty> = acc;
<.s:next(f, r)> -> fn(f, loop(r))};
.s:do fn -> rec loop(self) {<.s:empty> = .:ok;
<.s:next(f, r)> -> do fn(f); loop(r)};
.s:inject -> self.s:foldl;
];
[1, 2, 3].s:do println;
[1, 2, 3].s:inject 0 {acc, each -> acc + each}; "6"
[1, 2, 3].s:inject 0 (binop +) "6"
[1, 2, 3].s:map (1 +) "[2, 3, 4]"
[1, 2, 3].s:map <+ 2> "[3, 4, 5]"
[1, 2, 3].s:map .:negated "an error"
[1, 2, 3].s:map <.:negated> "[-1, -2, -3]"