Skip to content

Commit

Permalink
Merge pull request #554 from davidpcaldwell/issue/#176
Browse files Browse the repository at this point in the history
#176: Extract fp Stream to separate script
  • Loading branch information
davidpcaldwell authored Jun 26, 2022
2 parents 6a87445 + 6476fd1 commit bd94ed1
Show file tree
Hide file tree
Showing 12 changed files with 359 additions and 260 deletions.
6 changes: 3 additions & 3 deletions jsh/script/plugin.jsh.fifty.ts
Original file line number Diff line number Diff line change
Expand Up @@ -338,7 +338,7 @@ namespace slime.jsh.script {
fifty.tests.cli.invocation = function() {
const subject = test.subject;

var parser = fifty.$api.Function.pipe(
var parser = fifty.global.$api.Function.pipe(
subject.cli.option.string({ longname: "foo" })
);
var was = fifty.global.jsh.unit.$slime;
Expand All @@ -365,7 +365,7 @@ namespace slime.jsh.script {
};

fifty.tests.cli.run = function() {
const $api = fifty.$api;
const $api = fifty.global.$api;
const subject = test.subject;

var was: cli.Invocation<any>;
Expand Down Expand Up @@ -423,7 +423,7 @@ namespace slime.jsh.script {
};

fifty.tests.cli.wrap = function() {
const $api = fifty.$api;
const $api = fifty.global.$api;
var result: { status: number } = fifty.global.jsh.shell.jsh({
shell: fifty.global.jsh.shell.jsh.src,
script: fifty.jsh.file.object.getRelativePath("test/cli.jsh.js").file,
Expand Down
149 changes: 149 additions & 0 deletions loader/$api-Function-stream.fifty.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
// LICENSE
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not
// distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
//
// END LICENSE

namespace slime.$api.fp {
export interface Stream<T> {
iterate: () => {
next: Maybe<T>,
remaining: Stream<T>
}
}

export interface Exports {
Stream: {
from: {
empty: <T>() => Stream<T>
array: <T>(ts: T[]) => Stream<T>
}

//flatMap: <T,R>(map: (t: T) => R) => (stream: Stream<Maybe<T>>) => Stream<R>

first: <T>(ts: Stream<T>) => Maybe<T>

find: {
<W,N extends W>(predicate: (w: W) => w is N): (ws: Stream<W>) => Maybe<N>
<T>(predicate: Predicate<T>): (ts: Stream<T>) => Maybe<T>
}

filter: {
<W,N extends W>(predicate: (w: W) => w is N): (ws: Stream<W>) => Stream<N>
<T>(predicate: Predicate<T>): (ts: Stream<T>) => Stream<T>
}

collect: <T>(ts: Stream<T>) => T[]
}
}

(
function(
fifty: slime.fifty.test.Kit
) {
const { verify } = fifty;
const { $api } = fifty.global;

fifty.tests.suite = function() {
fifty.run(function testEmptyStream() {
var stream = $api.Function.Stream.from.empty();
var first = $api.Function.Stream.first(stream);
verify(first).present.is(false);
var collected = $api.Function.Stream.collect(stream);
verify(collected).length.is(0);
});

fifty.run(function testArrayStream() {
var array = [1,2,3,4];
var stream = $api.Function.Stream.from.array(array);
var first = $api.Function.Stream.first(stream);
verify(first).present.is(true);
verify(first).evaluate.property("value").is(1);
var collected = $api.Function.Stream.collect(stream);
verify(collected).length.is(4);
verify(collected)[0].is(1);
verify(collected)[1].is(2);
verify(collected)[2].is(3);
verify(collected)[3].is(4);
});

function streamRange(start: number, limit: number): Stream<number> {
function emptyStream(): Stream<number> {
return {
iterate: function() {
return {
next: null,
remaining: emptyStream()
}
}
}
}

return {
iterate: function() {
if (start < limit) {
return {
next: $api.Function.Maybe.value(start),
remaining: streamRange(start+1, limit)
}
} else {
return {
next: $api.Function.Maybe.nothing(),
remaining: emptyStream()
}
}
}
}
}

function streamTo(limit: number): Stream<number> {
return streamRange(0, limit);
}

var isOdd: Predicate<number> = (n: number) => n % 2 == 1;

fifty.run(function testStream() {
var rv = $api.Function.Stream.collect(streamTo(10));
verify(rv.join(",")).is("0,1,2,3,4,5,6,7,8,9")
});

fifty.run(function testFilter() {
var rv = $api.Function.result(
streamTo(10),
$api.Function.pipe(
$api.Function.Stream.filter(isOdd),
$api.Function.Stream.collect
)
);
verify(rv.join(",")).is("1,3,5,7,9");
});

fifty.run(function testFind() {
var firstOdd = $api.Function.result(
streamTo(10),
$api.Function.Stream.find(isOdd)
);
verify(firstOdd).present.is(true);
verify(firstOdd).evaluate.property("value").is(1);

var firstOver10 = $api.Function.result(
streamTo(10),
$api.Function.Stream.find(function(v) { return v > 10; })
);
verify(firstOver10).present.is(false);
});
}
}
//@ts-ignore
)(fifty);
}

namespace slime.$api.fp.internal.stream {
export interface Context {
$f: slime.$api.Global["Function"]
}

export type Exports = slime.$api.fp.Exports["Stream"];

export type Script = slime.loader.Script<Context,Exports>
}
111 changes: 111 additions & 0 deletions loader/$api-Function-stream.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
// LICENSE
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not
// distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
//
// END LICENSE

//@ts-check
(
/**
*
* @param { slime.$api.fp.internal.stream.Context } $context
* @param { slime.loader.Export<slime.$api.fp.internal.stream.Exports> } $export
*/
function($context,$export) {
var $f = $context.$f;

$export({
from: {
empty: function() {
return {
iterate: function() {
return {
next: $f.Maybe.nothing(),
remaining: $f.Stream.from.empty()
}
}
}
},
array: function(array) {
/**
* @template { any } T
* @param { T[] } array
* @param { number } index
* @returns { slime.$api.fp.Stream<T> }
*/
var ArrayStream = function recurse(array,index) {
return {
iterate: function() {
if (index < array.length) {
return {
next: $f.Maybe.value(array[index]),
remaining: recurse(array, index+1)
}
} else {
return {
next: $f.Maybe.nothing(),
remaining: $f.Stream.from.empty()
}
}
}
}
}

return ArrayStream(array, 0);
}
},
first: function(stream) {
return stream.iterate().next;
},
collect: function(stream) {
var rv = [];
var more = true;
while(more) {
var current = stream.iterate();
if (current.next.present) {
rv.push(current.next.value);
stream = current.remaining;
} else {
more = false;
}
}
return rv;
},
filter: function filter(predicate) {
return function(stream) {
return {
iterate: function() {
while(true) {
var current = stream.iterate();
if (!current.next.present) {
return {
next: $f.Maybe.nothing(),
remaining: $f.Stream.from.empty()
}
}
if (current.next.present && predicate(current.next.value)) {
return {
next: current.next,
remaining: filter(predicate)(current.remaining)
}
} else {
stream = current.remaining;
}
}
}
}
}
},
find: function find(predicate) {
return $f.pipe(
$f.Stream.filter(predicate),
$f.Stream.first
)
}//,
// flatMap: function(map) {
// throw new Error("Unimplemented");
// }
});
}
//@ts-ignore
)($context,$export);
Loading

0 comments on commit bd94ed1

Please sign in to comment.