Skip to content

Commit

Permalink
feat: reorganize contrib, implement stream, simplify optic
Browse files Browse the repository at this point in the history
This is a fairly large commit. The primary goals were to recreate the dux state
management library on top of fun. This led down a rabbit whole that included:

1. Implementing and writing tests for DatumEither
2. Exploring various stream implementations, including mostjs, rxjs, most2, and
   callbags.
3. Implementing a Stream type in fun.
4. Implementing Dux on top of Stream and DatumEither

In addition to adding Stream, Dux, and DatumEither I settled on a simpler model
for "contributions". Previous external contributions lived in the contrib
directory. These were anything from experimental ideas to algebraic structure
wrappers around other libraries. Ultimately, the purpose of fun is to be
dependency free, so external libraries were removed and "ideas" were either
promoted to full fledged modules or moved into the "ideas" directory. This
simplifies fun by making "ideas" non-production.

There is still some overlap between ideas and modules marked as experimental.
The basic idea is that modules in the ideas directoey  may or may not be published,
but modules in the root directory are definitely published. Experimental tagged
modules indicate that the api for those modules is considered unstable.

In contrib, the mostjs and fast-check modules were removed. Free was promoted to
a root module, and dux was moved to ideas. There were a handful of small api
additions to various modules such as promise, array, and async_iterable.

Lastly, the Optic type in optic.ts was simplified. Previously, Optic was broken
into an intersection of Viewer and  Modifier, with Reviewer being a separate
type. I found a way to properly do an optional include of reviewer in the root
Optic type so the subtypes of Viewer, Modifier, and Reviewer were removed. The
benefit of this is that the standard compose function in optic.ts will properly
compose a review function if it exists on the optics being composed.

may or may not be puli
  • Loading branch information
baetheus committed May 14, 2024
1 parent f0354db commit 8386713
Show file tree
Hide file tree
Showing 37 changed files with 4,545 additions and 1,704 deletions.
18 changes: 7 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ documentation can be found [here](https://jsr.io/@baetheus/fun). Following is a
list of the
[algebraic data types](https://en.wikipedia.org/wiki/Algebraic_data_type) and
[algebraic structures](https://en.wikipedia.org/wiki/Algebraic_structure)/[type classes](https://en.wikipedia.org/wiki/Type_class)
that are implemented in fun. Note that some of these types are bote data
that are implemented in fun. Note that some of these types are both data
structures and more general algebraic structures.

| Type | Algebraic Data Type | Algebraic Structure | Native | Other Names |
Expand Down Expand Up @@ -102,6 +102,7 @@ structures and more general algebraic structures.
| [Predicate](./predicate.ts) || | | |
| [Refinement](./refinement.ts) || | | |
| [State](./state.ts) || | | |
| [Stream](./stream.ts) || | | Observable |
| [Sync](./sync.ts) || | | IO |
| [SyncEither](./sync_either.ts) || | | IOEither |
| [These](./these.ts) || | | |
Expand All @@ -110,10 +111,10 @@ structures and more general algebraic structures.
## Major Versions

In the fashion of semantic versioning function makes an effort to not break APIs
on minor or patch releases. Occasionally, candidate tags (2.0.0-alpha.1) will be
used to indicate a commit is ready for inspection by other developers of fun.
The main branch of fun is for bleeding edge developement and is not considered
to be a production ready import.
on minor or patch releases. Occasionally, candidate tags (eg. 2.0.0-alpha.1)
will be used to indicate a commit is ready for inspection by other developers of
fun. The main branch of fun is for bleeding edge developement and is not
considered to be a production ready import.

| Version | Deno Release | TypeScript Version |
| ------- | --------------------------------------------------------------- | -------------------------------------------------------------------- |
Expand All @@ -125,7 +126,7 @@ to be a production ready import.
functional started as an exploratory project in late 2020 to learn more about
higher kinded type implementations in TypeScript and to assess how much effort
it would take to port fp-ts to a Deno-native format. Through that process it
became clear that the things I had learned could serve as both a useful tool and
became clear that the things I had learned could serve as both useful tools and
as a learning resource in and of itself. At various times functional has used
multiple hkt encodings, type class definitions, and implementation methods. Some
of the key history moments of functional are in the hkts history. Specifically,
Expand Down Expand Up @@ -169,9 +170,4 @@ start. Last, if you wish to add a feature it's good to start a discussion about
the feature with a few concrete use cases before submitting a PR. This will
allow for others to chime in without crowding the issues section.

Also, primary development takes places on one of my servers where I use fossil
instead of git as a VCS. I still use github for interfacing with users and for
releases, but if you wish to become a long term contributor learning to get
around with fossil is a must.

Thanks for you interest!
24 changes: 18 additions & 6 deletions applicable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,25 @@ import type { Wrappable } from "./wrappable.ts";
*
* @since 2.0.0
*/
export interface Applicable<U extends Kind>
extends Mappable<U>, Wrappable<U>, Hold<U> {
readonly apply: <A, B = never, C = never, D = unknown, E = unknown>(
export interface Applicable<
U extends Kind,
> extends Mappable<U>, Wrappable<U>, Hold<U> {
readonly apply: <
A,
B = never,
C = never,
D = unknown,
E = unknown,
>(
ta: $<U, [A, B, C], [D], [E]>,
) => <I, J = never, K = never>(
tfai: $<U, [(value: A) => I, J, K], [D], [E]>,
) => $<U, [I, B | J, C | K], [D], [E]>;
) => <
I,
J = never,
K = never,
L = unknown,
>(
tfai: $<U, [(value: A) => I, J, K], [L], [E]>,
) => $<U, [I, B | J, C | K], [D & L], [E]>;
}

/**
Expand Down
53 changes: 40 additions & 13 deletions array.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ export type AnyNonEmptyArray = NonEmptyArray<any>;
*
* @since 2.0.0
*/
export interface KindArray extends Kind {
export interface KindReadonlyArray extends Kind {
readonly kind: ReadonlyArray<Out<this, 0>>;
}

Expand Down Expand Up @@ -849,7 +849,7 @@ type Sequence<U extends Kind, R extends AnySub<U>[]> = $<U, [
*
* @since 2.0.0
*/
export type SequenceArray<U extends Kind> = <US extends AnySub<U>[]>(
export type SequenceArray<U extends Kind> = <const US extends AnySub<U>[]>(
...uas: US
) => Sequence<U, US>;

Expand All @@ -874,7 +874,7 @@ export type SequenceArray<U extends Kind> = <US extends AnySub<U>[]>(
*/
export function sequence<V extends Kind>(
A: Applicable<V>,
): <VS extends AnySub<V>[]>(
): <const VS extends AnySub<V>[]>(
...ua: VS
) => Sequence<V, VS> {
// deno-lint-ignore no-explicit-any
Expand Down Expand Up @@ -1203,6 +1203,33 @@ export function binarySearch<A>(
};
}

export function monoSearch<A>(
{ sort }: Sortable<A>,
): (value: A, sorted: ReadonlyArray<A>) => number {
return (value, sorted) => {
if (sorted.length === 0) {
return 0;
}

let bot = 0;
let mid: number;
let top = sorted.length;
let ordering;

while (top > 1) {
mid = Math.floor(top / 2);
ordering = sort(value, sorted[bot + mid]);

if (ordering >= 0) {
bot += mid;
}
top -= mid;
}

return sort(value, sorted[bot]) === 0 ? bot : bot + 1;
};
}

/**
* Given an Sortable<A> construct a curried insert function that inserts values into
* a new array in a sorted fashion. Internally this uses binarySearch to find
Expand Down Expand Up @@ -1474,7 +1501,7 @@ export function getInitializableArray<A = never>(): Initializable<
/**
* @since 2.0.0
*/
export const ApplicableArray: Applicable<KindArray> = {
export const ApplicableArray: Applicable<KindReadonlyArray> = {
apply,
map,
wrap,
Expand All @@ -1483,7 +1510,7 @@ export const ApplicableArray: Applicable<KindArray> = {
/**
* @since 2.0.0
*/
export const FilterableArray: Filterable<KindArray> = {
export const FilterableArray: Filterable<KindReadonlyArray> = {
filter,
filterMap,
partition,
Expand All @@ -1493,7 +1520,7 @@ export const FilterableArray: Filterable<KindArray> = {
/**
* @since 2.0.0
*/
export const FlatmappableArray: Flatmappable<KindArray> = {
export const FlatmappableArray: Flatmappable<KindReadonlyArray> = {
wrap,
map,
apply,
Expand All @@ -1503,17 +1530,17 @@ export const FlatmappableArray: Flatmappable<KindArray> = {
/**
* @since 2.0.0
*/
export const MappableArray: Mappable<KindArray> = { map };
export const MappableArray: Mappable<KindReadonlyArray> = { map };

/**
* @since 2.0.0
*/
export const FoldableArray: Foldable<KindArray> = { fold };
export const FoldableArray: Foldable<KindReadonlyArray> = { fold };

/**
* @since 2.0.0
*/
export const TraversableArray: Traversable<KindArray> = {
export const TraversableArray: Traversable<KindReadonlyArray> = {
map,
fold,
traverse,
Expand All @@ -1522,19 +1549,19 @@ export const TraversableArray: Traversable<KindArray> = {
/**
* @since 2.0.0
*/
export const WrappableArray: Wrappable<KindArray> = { wrap };
export const WrappableArray: Wrappable<KindReadonlyArray> = { wrap };

/**
* @since 2.0.0
*/
export const tap: Tap<KindArray> = createTap(FlatmappableArray);
export const tap: Tap<KindReadonlyArray> = createTap(FlatmappableArray);

/**
* @since 2.0.0
*/
export const bind: Bind<KindArray> = createBind(FlatmappableArray);
export const bind: Bind<KindReadonlyArray> = createBind(FlatmappableArray);

/**
* @since 2.0.0
*/
export const bindTo: BindTo<KindArray> = createBindTo(MappableArray);
export const bindTo: BindTo<KindReadonlyArray> = createBindTo(MappableArray);
41 changes: 30 additions & 11 deletions async_iterable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,15 @@ export function fromIterable<A>(ta: Iterable<A>): AsyncIterable<A> {
});
}

/**
* @since 2.2.0
*/
export function fromPromise<A>(ua: Promise<A>): AsyncIterable<A> {
return asyncIterable(async function* () {
yield await ua;
});
}

/**
* @since 2.0.0
*/
Expand All @@ -70,6 +79,21 @@ export function range(
});
}

export function loop<A, B, S>(
stepper: (state: S, value: A) => [S, B],
seed: S,
): (ua: AsyncIterable<A>) => AsyncIterable<B> {
return (ua) =>
asyncIterable(async function* () {
let hold: S = seed;
for await (const a of ua) {
const [next, value] = stepper(hold, a);
hold = next;
yield value;
}
});
}

/**
* @since 2.0.0
*/
Expand Down Expand Up @@ -340,18 +364,13 @@ export function takeWhile<A>(
* @since 2.0.0
*/
export function scan<A, O>(
foldr: (accumulator: O, value: A, index: number) => O,
initial: O,
scanner: (accumulator: O, value: A) => O,
seed: O,
): (ta: AsyncIterable<A>) => AsyncIterable<O> {
return (ta) =>
asyncIterable(async function* () {
let result = initial;
let index = 0;
for await (const a of ta) {
result = foldr(result, a, index++);
yield result;
}
});
return loop((accumulator, value) => {
const result = scanner(accumulator, value);
return [result, result];
}, seed);
}

/**
Expand Down
17 changes: 17 additions & 0 deletions benchmarks/array.bench.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import * as A from "../array.ts";
import * as N from "../number.ts";
import { pipe } from "../fn.ts";

const count = 100_000;
const sorted = A.range(count);
const binarySearch = A.binarySearch(N.SortableNumber);
const monoSearch = A.monoSearch(N.SortableNumber);
const searches = pipe(A.range(count), A.map(() => Math.random() * count));

Deno.bench("array binarySearch", { group: "binarySearch" }, () => {
searches.forEach((value) => binarySearch(value, sorted));
});

Deno.bench("array monoSearch", { group: "binarySearch" }, () => {
searches.forEach((value) => monoSearch(value, sorted));
});
102 changes: 102 additions & 0 deletions benchmarks/stream.bench.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import * as S from "../stream.ts";
import * as CB from "../ideas/callbag.ts";
import * as M from "npm:@most/[email protected]";
import * as MS from "npm:@most/[email protected]";
import * as R from "npm:[email protected]";
import { pipe } from "../fn.ts";

const count = 1_000_000;
const add = (a: number, b: number) => a + b;
const passthrough = (_: number, value: number) => value;
const runRx = <A>(obs: R.Observable<A>): Promise<void> =>
new Promise((resolve, reject) => {
obs.subscribe({ complete: resolve, error: reject });
});
function mostRange(count: number) {
return M.newStream<number>((snk) => {
let ended = false;
let index = -1;
while (++index < count) {
if (ended) {
break;
}
snk.event(0, index);
}
if (!ended) {
snk.end(0);
}
return {
dispose: () => {
ended = true;
},
};
});
}

Deno.bench("stream scan", { group: "scan" }, async () => {
await pipe(
S.range(count),
S.scan(add, 0),
S.scan(passthrough, 0),
S.runPromise(S.DefaultEnv),
);
});

Deno.bench("callbag scan", { group: "scan" }, async () =>
await pipe(
CB.range(count),
CB.scan(add, 0),
CB.scan(passthrough, 0),
CB.runPromise({ queueMicrotask }),
));

Deno.bench("most scan", { group: "scan" }, async () =>
await pipe(
mostRange(count),
M.scan(add, 0),
M.scan(passthrough, 0),
(stream) =>
M.runEffects(
stream,
MS.newDefaultScheduler(),
),
));

Deno.bench("rxjs scan", { group: "scan" }, async () => {
await pipe(
R.range(0, count),
R.scan(add, 0),
R.scan(passthrough, 0),
runRx,
);
});

const JOIN_COUNT = 1_000;

Deno.bench("stream join", { group: "join" }, async () => {
await pipe(
S.range(JOIN_COUNT),
S.flatmap(() => S.range(JOIN_COUNT)),
S.runPromise(S.DefaultEnv),
);
});

Deno.bench("most join", { group: "join" }, async () => {
await pipe(
mostRange(JOIN_COUNT),
M.chain(() => mostRange(JOIN_COUNT)),
(stream) =>
M.runEffects(
stream,
MS.newDefaultScheduler(),
),
);
});

Deno.bench("rxjs join", { group: "join" }, async () => {
await pipe(
R.range(0, JOIN_COUNT),
R.mergeMap(() => R.range(0, JOIN_COUNT)),
runRx,
);
});
Loading

0 comments on commit 8386713

Please sign in to comment.