Coder Social home page Coder Social logo

baetheus / fun Goto Github PK

View Code? Open in Web Editor NEW
100.0 10.0 6.0 1.59 MB

A collection of algebraic data types, optics, and data structures based on a light weight higher kinded type implementation. Written for deno.

Home Page: https://jsr.io/@baetheus/fun

License: MIT License

TypeScript 99.83% Shell 0.07% Nix 0.10%
monad typescript deno algebraic-data-types higher-kinded-types functor option either task effects

fun's Introduction

functional Coverage Status deno module JSR

Functional is a no dependency utility library for TypeScript and JavaScript that includes a full suite of functional programming tools. Recent versions have moved away from the jargon of Category Theory to a more intuitive naming scheme unique to functional. However, for those that do have existing experience with functional programming or category theory, functional includes many algebraic data types (including implementations of algebraic structures for native javascript structures), type classes, TypeScript-based higher kinded type substitutions, data-last utility combinators, and concrete type utilities.

The primary goals of functional are to be:

  • Pragmatic: The API surface of functional should favor ease-of-use and consistency over cleverness or purity. This project is ultimately for getting work done, even it if means a data structure or tools is not mathematically sound in all use cases.
  • Understandable: The higher kinded type and algebraic structure implementations are meant to be as simple as possible so their logic can be easily audited. We have also chosen descriptive names for TypeClass implementations instead of ones pulled from Category Theory.
  • Performant: Once the first two goals are satisfied, the long term changes within functional are likely going to be beneath the API surface and aimed at speeding things up where possible.

Some non-goals of functional are:

  • To be an exact port of fp-ts. Many changes have been implemented throughout functional that diverge sharply from fp-ts, this is often on purpose.

Usage

This library is a collection of smaller tools. This means that each one should be imported separately. There is no barrel export, instead one should pull in the modules they need individually. Following is an example importing from jsr using the deno runtime.

import * as A from "jsr:@baetheus/fun/array";
import { pipe } from "jsr:@baetheus/fun/fn";

pipe(
  A.range(5), // get 5 numbers 0-4
  A.map((n) => n * n), // square them
  console.log, // [ 0, 1, 4, 9, 16 ]
);

Documentation

Documentation is generated for each github tagged release. The latest documentation can be found here. Following is a list of the algebraic data types and algebraic structures/type classes 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
Applicable Applicative
Bimappable Bifunctor, Covariant Bifunctor
Combinable Semigroup
Comparable Setoid, Eq
Composable Category
Failable Validation
Filterable
Flatmappable Monad
Foldable Reducible
Initializable Monoid
Mappable Functor, Covariant Functor
Premappable Contravariant, Contravariant Functor
Schemable
Showable Show
Sortable Ord
Traversable
Wrappable Pointed
Newtype Brand, Branded Type
AsyncIterable
Boolean
Iterable
Number
Promise
ReadonlyArray Array
ReadonlyMap Map
ReadonlySet Set
String
Async Task
AsyncEither TaskEither
Decoder
Either
Fn Reader
FnEither ReaderEither
Identity Trivial
JsonSchema
Nilable
Optic Iso, Lens, Optional, Prism, Traversal
Option Maybe
Pair Separated
Predicate
Refinement
State
Stream Observable
Sync IO
SyncEither IOEither
These
Tree

Major Versions

In the fashion of semantic versioning function makes an effort to not break APIs 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
2.0.0 1.36.0 5.1.6
1.0.0 1.9.2 4.2.2

History

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 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, the hkts implementation in the initial commit and the last major type system rewrite might be interesting. Now, however, the API for version 1.0.0 is set and will only change between major versions (which should be extremely rare).

This project is incredibly indebted to gcanti, pelotom, and the TypeScript community at large. There is nothing new in this project, it's all a reimaginings of ideas that already existed.

For anyone getting started with functional programming I highly recommend writing your own implementation of an ADT such as Option or Either. From Functor to IndexedTraversable with everything inbetween, there is a lot to learn about the mechanics of programming in general by taking these small pieces apart and putting them back together.

Contributions

Contributions are welcome! Currently, the only maintainer for functional is baetheus. If you want to add to functional or change something, open an issue and ask away. The guidelines for contribution are:

  1. We are kind to others
  2. We use conventional commit messages
  3. We use semantic versioning
  4. We try to keep test coverage at 100%
  5. We try not to break APIs between major releases

Since there is little churn in this library releases are infrequent. If you wish to contribute I would prefer that you start with documentation. It is one of my long term goals to have a few sentences of description and an example for every export. After that, if I'm behind on test coverage that is a great place to 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.

Thanks for you interest!

fun's People

Contributors

baetheus avatar web-flow avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

fun's Issues

AsyncIterable 2.0

Standard Module Implementation for async_iterable.ts

  • Add file block comment at top of file describing the adt/structure.
  • Check for any missing combinators/derivations that could be implemented.
  • Modify all combinator variable names to be readable (use style guide).
  • Add documentation to every export (and // comment local tools).
  • Check that every function and line is covered by tests in the testing directory.

Style Guide

  • Use A -> I, B -> J, C -> K, D -> L for inner types.
  • Use ua, va to match URI, VRI, etc in generic positions.
  • Implement algebraic structure types (type classes) out to length 4.
  • Use functions over const arrow functions for root exports.
  • Document all type exports.
  • Order file exports by group and then alphabetically. Groups are Types, Constructors, Combinators, Type Class Instances, Type Class Constructors, and Derived Combinators.

Documentation Guide

  • Start with at least one full sentence describing the use case for the export.
  • Include a @SInCE tag (default to 2.0.0)
  • Include an @example tag with a simple example
    • Imports should be relative for functional stuff and otherwise full urls with versions
    • Example should end with at least one assert from the deno std lib

Why no ApplicativeSeq for Task?

Am I missing something?

Sometimes when traversing you'd like it done sequentially, in my case because of rate-limiting reasons. TaskEither has an ApplicativeSeq, why not Task?

Building for NPM

As a developer I would like to be able to use functional both in a Deno and Node context.
To achieve this I have did a bit of research and found that the deno team has released dnt which makes it rather straightforward to enable publishing npm packages from deno as a source.

This could be discussed in the context of release management.

We have briefly touched on it previously and as long as the repository has the right NPM credentials set up (for the nullpub org) it should be pretty self-evident to publish the package to NPM too.

Feature: Branded types like newtype-ts

Let me start by asking, is there interest in them, and if so is this library the place?

I currently have an example of wanting to brand a iso datetime string:

/** ISO 8601 Datetime string */
type DateTimeString = string;

const dateTimeString: D.Decoder<DateTimeString> = (i: unknown) =>
  G.string(i) && !Number.isNaN(new Date(i))
    ? D.success(i)
    : D.failure(i, "not a parseable date");

const UserV2 = D.struct({
  accountCreatedAt: dateTimeString,
  bio: D.string,
  firstSeen: D.number,
  followedByUsm: D.boolean,
  followersCount: D.number,
  followingCount: D.number,
  handle: D.string,
  lastSeen: D.number,
  name: D.string,
  partOfFamList: D.boolean,
  profileImageUrl: D.string,
  tweetCount: D.number,
  twitterId: D.string,
});

type UserV2 = D.TypeOf<typeof UserV2>;

This is something but unlike branded types or newtypes the type engine doesn't actually do anything to help you make sure you're not working with a random string, apart from this decoder trying to ensure this is the case at runtime.

Does someone have a way they're handling this now in their Deno code? If no one does, is there a need? And if there's a need, should we fulfill it with this library? (:

Apply 2.0

Standard Module Implementation for apply.ts

  • Add file block comment at top of file describing the adt/structure.
  • Check for any missing combinators/derivations that could be implemented.
  • Modify all combinator variable names to be readable (use style guide).
  • Add documentation to every export (and // comment local tools).
  • Check that every function and line is covered by tests in the testing directory.

Style Guide

  • Use A -> I, B -> J, C -> K, D -> L for inner types.
  • Use ua, va to match URI, VRI, etc in generic positions.
  • Implement algebraic structure types (type classes) out to length 4.
  • Use functions over const arrow functions for root exports.
  • Document all type exports.
  • Order file exports by group and then alphabetically. Groups are Types, Constructors, Combinators, Type Class Instances, Type Class Constructors, and Derived Combinators.

Documentation Guide

  • Start with at least one full sentence describing the use case for the export.
  • Include a @SInCE tag (default to 2.0.0)
  • Include an @example tag with a simple example
    • Imports should be relative for functional stuff and otherwise full urls with versions
    • Example should end with at least one assert from the deno std lib

Restructure pipeable/module implementations for all adts.

Currently, the Type Class instances contain the canonical implementation of their associated functions. ie (for option.ts):

export const Functor: TC.Functor<URI> = {
    map: (fai) => (ta) => isNone(ta) ? ta : some(fai(ta.value)),
};

export const { map } = Functor;

Currently, the deno docs tool doesn't like the typing on export const constructs, let alone the destructured export consts. Thus, this issue if meant to track migrating the canonical map, ap, of, etc implementations to export functions like so:

export function map<A, I>(fai: (a: A) => I): ((ta: Option<A>) => Option<I>) {
    return ta => isLeft(ta) ? ta : some(fai(ta.value));
}

export const Functor: TC.Functor<URI> = { map };

This should allow us to pick up at least the type information in the automatic doc generation. Incidentally, this will make the types on the pipeable exports look nicer too. The downside is that the types for each function export must be declared manually for every function (as opposed to inferred from the eg TC.Functor<URI> type.)

Alt 2.0

Standard Module Implementation for Alt

  • Add file block comment at top of file describing the adt/structure.
  • Check for any missing combinators/derivations that could be implemented.
  • Modify all combinator variable names to be readable (use style guide).
  • Add documentation to every export (and // comment local tools).
  • Check that every function and line is covered by tests in the testing directory.

Style Guide

  • Use A -> I, B -> J, C -> K, D -> L for inner types.
  • Use ua, va to match URI, VRI, etc in generic positions.
  • Implement algebraic structure types (type classes) out to length 4.
  • Use functions over const arrow functions for root exports.
  • Document all type exports.
  • Order file exports by group and then alphabetically. Groups are Types, Constructors, Combinators, Type Class Instances, Type Class Constructors, and Derived Combinators.

Documentation Guide

  • Start with at least one full sentence describing the use case for the export.
  • Include a @SInCE tag (default to 2.0.0)
  • Include an @example tag with a simple example
    • Imports should be relative for functional stuff and otherwise full urls with versions
    • Example should end with at least one assert from the deno std lib

Fix: array.ts should use Array as the primary type for inputs and outputs.

I wouldn't say I feel strongly about this but I thought I'd at least put it up so people can vote on it.

For me the annoyance of having to change branches to all be readonly or safely cast the array to be writable again is not worth the bit of extra safety. Or I'd at least prefer there would be a separate module for readonly producing functions, like you often see with nonemptyarray. I'd also assume that all functions in Array don't mutate their inputs making the readonly less useful.

Create a nix flake for standard development tasks.

Create a nix flake with packages for build/deployment to npm, testing, coverage reporting, and whatever else we end up needing.

This will also require documentation in the README for other developers. (probably basic nixFlakes install and now to run the packages).

Contravariant 2.0

Standard Module Implementation for contravariant.ts

  • Add file block comment at top of file describing the adt/structure.
  • Check for any missing combinators/derivations that could be implemented.
  • Modify all combinator variable names to be readable (use style guide).
  • Add documentation to every export (and // comment local tools).
  • Check that every function and line is covered by tests in the testing directory.

Style Guide

  • Use A -> I, B -> J, C -> K, D -> L for inner types.
  • Use ua, va to match URI, VRI, etc in generic positions.
  • Implement algebraic structure types (type classes) out to length 4.
  • Use functions over const arrow functions for root exports.
  • Document all type exports.
  • Order file exports by group and then alphabetically. Groups are Types, Constructors, Combinators, Type Class Instances, Type Class Constructors, and Derived Combinators.

Documentation Guide

  • Start with at least one full sentence describing the use case for the export.
  • Include a @SInCE tag (default to 2.0.0)
  • Include an @example tag with a simple example
    • Imports should be relative for functional stuff and otherwise full urls with versions
    • Example should end with at least one assert from the deno std lib

Applicative 2.0

Standard Module Implementation for applicative.ts

  • Add file block comment at top of file describing the adt/structure.
  • Check for any missing combinators/derivations that could be implemented.
  • Modify all combinator variable names to be readable (use style guide).
  • Add documentation to every export (and // comment local tools).
  • Check that every function and line is covered by tests in the testing directory.

Style Guide

  • Use A -> I, B -> J, C -> K, D -> L for inner types.
  • Use ua, va to match URI, VRI, etc in generic positions.
  • Implement algebraic structure types (type classes) out to length 4.
  • Use functions over const arrow functions for root exports.
  • Document all type exports.
  • Order file exports by group and then alphabetically. Groups are Types, Constructors, Combinators, Type Class Instances, Type Class Constructors, and Derived Combinators.

Documentation Guide

  • Start with at least one full sentence describing the use case for the export.
  • Include a @SInCE tag (default to 2.0.0)
  • Include an @example tag with a simple example
    • Imports should be relative for functional stuff and otherwise full urls with versions
    • Example should end with at least one assert from the deno std lib

Add zip function to array module

fp-ts provides a zip function in the array module.

Does fun provide something similar, or should I create a PR with a zip implementation.

Comonad 2.0

Standard Module Implementation for comonad.ts

  • Add file block comment at top of file describing the adt/structure.
  • Check for any missing combinators/derivations that could be implemented.
  • Modify all combinator variable names to be readable (use style guide).
  • Add documentation to every export (and // comment local tools).
  • Check that every function and line is covered by tests in the testing directory.

Style Guide

  • Use A -> I, B -> J, C -> K, D -> L for inner types.
  • Use ua, va to match URI, VRI, etc in generic positions.
  • Implement algebraic structure types (type classes) out to length 4.
  • Use functions over const arrow functions for root exports.
  • Document all type exports.
  • Order file exports by group and then alphabetically. Groups are Types, Constructors, Combinators, Type Class Instances, Type Class Constructors, and Derived Combinators.

Documentation Guide

  • Start with at least one full sentence describing the use case for the export.
  • Include a @SInCE tag (default to 2.0.0)
  • Include an @example tag with a simple example
    • Imports should be relative for functional stuff and otherwise full urls with versions
    • Example should end with at least one assert from the deno std lib

Compiler fails to catch implicit any

Not sure if this is a bug or me doing something wrong.

Looking at the implementation of Task.map I would have thought the code below shouldn't pass the type checking.

Any ideas welcome 😇

import { serve } from "https://deno.land/[email protected]/http/server.ts";
import { pipe } from "https://deno.land/x/[email protected]/fns.ts";
import * as T from "https://deno.land/x/[email protected]/task.ts";

type User = {
  name: string;
  mail: string;
};

const render = (user: User) => `Name: ${user.name}, email: ${user.mail}`;

const getJson = (url: string) => async () => {
  const jsonResponse = await fetch(url);
  const jsonData = await jsonResponse.json();
  console.log(jsonData);
  return jsonData;
};

const handler = () =>
  pipe(
    "https://api.github.com/users/denoland",
    getJson, // This returns a Task<any>
    T.map(render), // Why isn't the compiler noticing that `render` needs to be passed a `User`?
    T.map((s) => new Response(s))
  );

await serve(handler());

How to use schemable nullable properly?

Example definition:

import * as S from "https://raw.githubusercontent.com/nullpub/fun/d5f3eb4cb26ff8c7e66d84208f4d1228f0891ed7/schemable/schemable.ts";

export const Good = S.make((s) => s.struct({
  name: s.string(),
  vat: s.nullable(s.number()),
}));

ends up with:

error: TS2322 [ERROR]: Type 'Kind<keyof Kinds<any[]>, [number | null, null, never, never]>' is not assignable to type 'Kind<keyof Kinds<any[]>, [number | null, never, never, never]>'.
  Type 'State<null, number | null>' is not assignable to type 'Kind<keyof Kinds<any[]>, [number | null, never, never, never]>'.
    Type 'State<null, number | null>' is not assignable to type 'State<never, number | null>'.
      Call signature return types '[number | null, null]' and '[number | null, never]' are incompatible.
        Type 'null' is not assignable to type 'never'.
	vat: s.nullable(s.number()),

Create release generation CI/CD

On push/merge of a commit containing the release: conventional commit message, generate a zip file or some other release artifact that is added to the github releases list.

Overlaps with #39 .

Do Notation - Post 2.0

Regarding Affect.

Was wondering if we're supposed to execute it straight up on receiving Ctx or would it make sense to be more like RTE and actually return a Task there.

I know you went for this implementation originally because you dislike the Reader<Task<Either>> ending up in a thunk that still needs to be executed, but on the other side it's quite handy that the computation stays lazy.

Also am wondering what's wrong with the following snippet (that imho should work)

import { assertEquals } from "https://deno.land/std/testing/asserts.ts";
import * as A from "./affect.ts";
import { identity, pipe } from "./fns.ts";

const aff1 = pipe(A.ask<string>(), A.map((a) => a.toUpperCase()));
const aff2 = A.of<string>("b");
const add = ({ a, b }: { a: string; b: string }) => a + b;

const x = pipe(
  aff1,
  A.bindTo("a"),
  A.bind("b", () => aff2),
  A.map(add),
  A.fold((_) => "aa", identity),
)("a");

const y = pipe(
  A.Do(),
  A.bind("a", () => aff1),
  A.bind("b", () => aff2),
  A.map(add),
  A.fold((_) => "aa", identity),
)("a");

assertEquals(await x, "Ab");
assertEquals(await y, "Ab");

Only after resorting to use any a second type parameter this starts to work correctly.

import { assertEquals } from "https://deno.land/std/testing/asserts.ts";
import * as A from "./affect.ts";
import { identity, pipe } from "./fns.ts";

const aff1 = pipe(A.ask<string, any>(), A.map((a) => a.toUpperCase()));
const aff2 = A.of<string, any, string>("b");
const add = ({ a, b }: { a: string; b: string }) => a + b;

const x = pipe(
  aff1,
  A.bindTo("a"),
  A.bind("b", () => aff2),
  A.map(add),
  A.fold((_) => "aa", identity),
)("a");

const y = pipe(
  A.Do(),
  A.bind("a", () => aff1),
  A.bind("b", () => aff2),
  A.map(add),
  A.fold((_) => "aa", identity),
)("a");

assertEquals(await x, "Ab");
assertEquals(await y, "Ab");

What am I doing wrong?

Well, I never really tried using Do notation for Affect, this is likely the first problem. I don't have much time right now, but for the x example I see the following hints from the first code block:

  1. aff1 has type Affect<string, never, string>
  2. aff2 has type Affect<never, never, string>
  3. In x if we remove the invoke with "x" we can step through the pipeline
  4. At A.bindTo("a") we have type Affect<string, never, { readonly a: string }>
  5. At A.bind("B", () => aff2) we have type Affect<never, never, { readonly a: string, readonly b: string }> This is the first problem, indicating a type error in bind for Affect.
  6. At A.map(add) we keep the input type of never, which tracks.

Looks like the type for createDo in derivations.ts is missing initialization to never for typeclasses of length 2, 3, and 4. If you wouldn't mind making a bug ticket referencing these two comments that'd be super helpful!

Originally posted by @baetheus in #50 (comment)

Datum 2.0

Standard Module Implementation for datum.ts

  • Add file block comment at top of file describing the adt/structure.
  • Check for any missing combinators/derivations that could be implemented.
  • Modify all combinator variable names to be readable (use style guide).
  • Add documentation to every export (and // comment local tools).
  • Check that every function and line is covered by tests in the testing directory.

Style Guide

  • Use A -> I, B -> J, C -> K, D -> L for inner types.
  • Use ua, va to match URI, VRI, etc in generic positions.
  • Implement algebraic structure types (type classes) out to length 4.
  • Use functions over const arrow functions for root exports.
  • Document all type exports.
  • Order file exports by group and then alphabetically. Groups are Types, Constructors, Combinators, Type Class Instances, Type Class Constructors, and Derived Combinators.

Documentation Guide

  • Start with at least one full sentence describing the use case for the export.
  • Include a @SInCE tag (default to 2.0.0)
  • Include an @example tag with a simple example
    • Imports should be relative for functional stuff and otherwise full urls with versions
    • Example should end with at least one assert from the deno std lib

Doctest are not actually tested. Only type-checked.

I assume wrong and the examples we provide are not actually running when we call deno test --doc
Therefore the following typechecks but is wrong:

/**
 * Fold away the inner Either from the `TaskEither` leaving us with the
 * result of our computation in the form of a `Task`
 *
 * ```ts
 * import { assertEquals } from "https://deno.land/std/testing/asserts.ts";
 * import * as TE from "./task_either.ts";
 * import * as E from "./either.ts";
 * import * as T from "./task.ts";
 * import { flow, identity } from "./fns.ts";
 *
 * const hello = flow(
 *  TE.fold(() => 'World', identity),
 *  T.map(name => `Hello ${name}!`),
 * );
 *
 * assertEquals(await hello(TE.right('Functional!'))(), E.right("Hello Functional!!"));
 * assertEquals(await hello(TE.left(Error))(), E.right("Hello World!"));
 * ```
 */
export function fold<L, R, B>(
  onLeft: (left: L) => B,
  onRight: (right: R) => B,
): (ta: TaskEither<L, R>) => Task<B> {
  return (ta) => () => ta().then(eitherFold<L, R, B>(onLeft, onRight));
}

Whereas the actually valid example code is:

/**
 * Fold away the inner Either from the `TaskEither` leaving us with the
 * result of our computation in the form of a `Task`
 *
 * ```ts
 * import { assertEquals } from "https://deno.land/std/testing/asserts.ts";
 * import * as TE from "./task_either.ts";
 * import * as T from "./task.ts";
 * import { flow, identity } from "./fns.ts";
 * 
 * const hello = flow(
 *   TE.fold(() => "World", identity),
 *   T.map((name) => `Hello ${name}!`),
 * );
 * 
 * assertEquals(await hello(TE.right("Functional!"))(), "Hello Functional!!");
 * assertEquals(await hello(TE.left(Error))(), "Hello World!");
 * ```
 */
export function fold<L, R, B>(
  onLeft: (left: L) => B,
  onRight: (right: R) => B,
): (ta: TaskEither<L, R>) => Task<B> {
  return (ta) => () => ta().then(eitherFold<L, R, B>(onLeft, onRight));
}

Extentions to `affect.ts`

Affect could use a few more combinators.

  • fromEither
  • fromOption
  • fromTask
  • fromTaskEither
  • fromIO
  • fromIOEither

Add sequence helpers to state.ts

The State ADT does not export sequenceTuple or sequenceStruct even though they would be easily constructed from the state Apply instance. Add those exports and tests for them.

Proposal: Generic Result ADT

There is currently a Result<E, T> ADT that is geared towards decoders.

Proposal
Create a more generic result type. In many production apps I found myself ending up with the rust-ish Result type.

Something along the lines of:

interface Ok<T> {
  _tag: "Ok";
  result: T;
  error: null;
}

interface Err<E> {
  _tag: "Err";
  error: E;
  result: null;
}

type Result<T, E> = Ok<T> | Err<E>;

const ok = <T, E>(result: T): Result<T, E> => ({
  _tag: "Ok",
  result,
  error: null,
});
const err = <T, E>(error: E): Result<T, E> => ({
  _tag: "Err",
  error,
  result: null,
});
function computation(a: number) {
  return E.tryCatch(() => a * 2, flow(String, Error));
}

const match = <L, R>(e: E.Either<L, R>) =>
  E.fold<L, R, Result<R, L>>(err, ok)(e);

const result: Result<number, Error> = pipe(computation(1), match);

chore: update readme

Many of the links in the readme are returning 404s. A good editing pass should fix this.

Roll back changes to TaskEither#tryCatch

In #31 we changed tryCatch for taskEither to:

declare function tryCatch<A, B>(task: Task<A>, onThrow: (e: unknown) => B): TaskEither<B, A>;

While working on #39 I finally remembered why I was against this change. The simple answer is that Task is never supposed to throw. None of the Task combinators handle the thrown side of the returned promise, which makes it exactly the wrong vehicle to wrap promise thunks that can throw. In actuality, a Task that can fail is no Task at all. While there may be functions in typescript with the type () => Promise<A>, if these functions can throw or their promises can throw then they shouldn't be Tasks.

First, let's disambiguate the various cases where we handle untyped thrown errors:

  • Handling synchronous functions that might throw type Function<AS extends unknown[], R> = (...as: AS) => R
  • Handling asyncronous functions that might throw (note that here there can be both synchronous and asynchronous throwing) type AsyncFunction<AS extends unknown[], R> = (...as: AS) => Promise<R>

We will want to define tryCatch-like functions for many of the ADTs in fun. Following is a list of the ADTs in fun where it makes sense to implement tryCatch. ADTs that should also handle async tryCatch will be marked with tryPromise:

  • Affect: tryCatch, tryPromise
  • Datum: tryCatch
  • Either: tryCatch
  • IO: tryCatch
  • IOEither: tryCatch
  • Nilable: tryCatch
  • Reader: tryCatch
  • ReaderEither: tryCatch
  • Task: tryCatch*, tryPromise
  • TaskEither: tryCatch*, tryPromise
  • These: tryCatch

Here, a * denotes that the non-thunk versions of tryCatch can cause some issues. Lets look at those (Task, TaskEither).

When implementing tryCatch for Task there is a natural implementation:

function taskTryCatch1<A>(fa: () => A, onThrow: (e: unknown) => A): Task<A> {
  return () => {
    try {
      return fa();
    } catch (e) {
      return onThrow(e);
    }
  }
}

But what if we want to turn a non-thunk into a Task? Then we might have:

function taskTryCatch2<AS extends unknown[], R>(
  fasr: (...as: AS) => R,
  onThrow: (e: unknown) => R
): (...as: AS) => Task<R> {
  return (...as) => () => {
    try {
      return fasr(...as);
    } catch (e) {
      return onThrow(e);
    }    
  }
}

Both of these are valid cases, but the second one covers the usage of the first as well, but ends up returning Task<Task<R>> when the function is a thunk. These cases repeat for promise returning functions:

function taskTryPromise1<A>(fa: () => Promise<A>, onThrow: (e: unknown) => A): Task<A> {
  return async () => {
    try {
      return await fa();
    } catch (e) {
      return onThrow(e);
    }    
  }
}

function taskTryPromise2<AS extends unknown[], R>(
  fasr: (...as: AS) => Promise<R>,
  onThrow: (e: unknown) => R
): (...as: AS) => Task<R> {
  return (...as) => async () => {
    try {
      return await fasr(...as);
    } catch (e) {
      return onThrow(e);
    }    
  }
}

After writing these implementations and many others I realized why this problem was likely seeming more complicated than it is. The example used as the reason for this change was this:

export const old_get_database_by_id = (database_id: string) =>
  pipe(
    () => databases.retrieve({ database_id }),
    TE.fromFailableTask(() => new Error(`Couldnt fetch db: ${database_id}`))
  );
  
export const new_get_database_by_id = (database_id: string) =>
  tryCatch(
    () => databases.retrieve({ database_id }),
    () => new Error(`Couldnt fetch db: ${database_id}`)
  );

Here the old way was problematic because it was trying to use tryCatch (which only accepted thunks) to map a promise returning function to TaskEither. If we had restated the question to: What is the most sensible way to take functions of the form type Input<AS extends unknown[], R> = (...as: AS) => Promise<R> and turn them into ADTs like Task, TaskEither, and Affect? Well, lets list the things we want tryCatch to do:

  1. It needs to wrap more than just thunks.
  2. It needs to catch errors--both synchronous and asynchronous.
  3. It should be consistent across ADTs.
  4. The error case should have access to the the arguments of the throwing function/promise.

Because of 1 I propose that idiomatic tryCatch shouldn't default to thunks. This means we would move away from taskTryCatch1 style implementations and towards taskTryCatch2.

Because of 2 I propose that we break tryCatch into a synchronous tryCatch version and an async tryPromise version. This is to avoid testing for Promises in both the result and throw cases of tryCatch.

Because of 4 the onThrow case should accept both the error and the arguments array. Here we have the choice of 1 (e: unknown, ...as: AS) or 2 (e: unknown, as: AS). For consistency reasons (and to cut down on an extra spread operation) I am tentatively going with 2.

This makes our implentations for Task and TaskEither as follows:

export function taskTryCatch<AS extends unknown[], R>(
  fasr: (...as: AS) => R,
  onThrow: (e: unknown, as: AS) => R,
): (...as: AS) => Task<R> {
  return (...as) =>
    handleThrow(
      () => fasr(...as),
      (r) => Promise.resolve(r),
      (e) => Promise.resolve(onThrow(e, as)),
    );
}

export function taskTryPromise<AS extends unknown[], R>(
  fasr: (...as: AS) => Promise<R>,
  onThrow: (e: unknown, as: AS) => R,
): (...as: AS) => Task<R> {
  return (...as) =>
    handleThrow(
      () => fasr(...as),
      (r) => r.catch((e) => onThrow(e, as)),
      (e) => Promise.resolve(onThrow(e, as)),
    );
}

export function taskEitherTryCatch<AS extends unknown[], A, B>(
  fasr: (...as: AS) => A,
  onThrow: (e: unknown, as: AS) => B,
): (...as: AS) => TaskEither<B, A> {
  return (...as) =>
    handleThrow(
      () => fasr(...as),
      (r) => Promise.resolve(right(r)),
      (e) => Promise.resolve(left(onThrow(e, as))),
    );
}

export function taskEitherTryPromise<AS extends unknown[], A, B>(
  fasr: (...as: AS) => Promise<A>,
  onThrow: (e: unknown, as: AS) => B,
): (...as: AS) => TaskEither<B, A> {
  return (...as) => {
    const _onThrow = (e: unknown) => left(onThrow(e, as));
    return handleThrow(
      () => fasr(...as),
      (r) => r.then(right).catch(_onThrow),
      (e) => Promise.resolve(_onThrow(e)),
    );
  };
}

and the usage of the original case looks something like this:

export const get_database_by_id = tryPromise(
  databases.retrieve,
  (_, { database_id }) => new Error(`Couldnt fetch db: ${database_id}`)
);

// Which returns the type:
type GetDatabaseReturn = (props: DBProps) => TaskEither<Error, DBReturnValue>

Boolean 2.0

Standard Module Implementation for boolean.ts

  • Add file block comment at top of file describing the adt/structure.
  • Check for any missing combinators/derivations that could be implemented.
  • Modify all combinator variable names to be readable (use style guide).
  • Add documentation to every export (and // comment local tools).
  • Check that every function and line is covered by tests in the testing directory.

Style Guide

  • Use A -> I, B -> J, C -> K, D -> L for inner types.
  • Use ua, va to match URI, VRI, etc in generic positions.
  • Implement algebraic structure types (type classes) out to length 4.
  • Use functions over const arrow functions for root exports.
  • Document all type exports.
  • Order file exports by group and then alphabetically. Groups are Types, Constructors, Combinators, Type Class Instances, Type Class Constructors, and Derived Combinators.

Documentation Guide

  • Start with at least one full sentence describing the use case for the export.
  • Include a @SInCE tag (default to 2.0.0)
  • Include an @example tag with a simple example
    • Imports should be relative for functional stuff and otherwise full urls with versions
    • Example should end with at least one assert from the deno std lib

Const 2.0

Standard Module Implementation for const.ts

  • Add file block comment at top of file describing the adt/structure.
  • Check for any missing combinators/derivations that could be implemented.
  • Modify all combinator variable names to be readable (use style guide).
  • Add documentation to every export (and // comment local tools).
  • Check that every function and line is covered by tests in the testing directory.

Style Guide

  • Use A -> I, B -> J, C -> K, D -> L for inner types.
  • Use ua, va to match URI, VRI, etc in generic positions.
  • Implement algebraic structure types (type classes) out to length 4.
  • Use functions over const arrow functions for root exports.
  • Document all type exports.
  • Order file exports by group and then alphabetically. Groups are Types, Constructors, Combinators, Type Class Instances, Type Class Constructors, and Derived Combinators.

Documentation Guide

  • Start with at least one full sentence describing the use case for the export.
  • Include a @SInCE tag (default to 2.0.0)
  • Include an @example tag with a simple example
    • Imports should be relative for functional stuff and otherwise full urls with versions
    • Example should end with at least one assert from the deno std lib

More testing golf after Deno 1.10 release

It looks like the testing harness for deno was completely changed in Release 1.10. After updating locally it seems that coverage has dropped below 100% (In some weird ways it turns out). The purpose of this ticket is to use the local scripts/coverage.sh along with Deno 1.10+ runtime to get back up to 100% coverage.

Proposal: extending TaskEither with fold

Using TaskEither a lot and currently that means writing a lot of the following code:

pipe(task_either, t => t(), then(E.fold(err, ok)))

This could be easily simplified to:

pipe(task_either, TE.fold(err, ok))

Proposed fold impl:

export function fold<L, R, B>(
  onLeft: (left: L) => B,
  onRight: (right: R) => B
): (ta: TaskEither<L, R>) => Task<B> {
  return (ta) =>
    pipe(
      ta(),
      then(eitherFold<L, R, B>(onLeft, onRight)),
      constant
    );
}

How to combine five TaskEither?

Say you have five TaskEither's. Four share the same Left error, one has its own. All five results need to go into a single response so you'd like to bind / map them into the same context.

I tried Do but it only allows binding three. How would you get five TaskEither results into the same function?

PR Validation

We should set up a github workflow for PR validation that should verify that:

  • There are no formatting changes to be applied with deno fmt --check
  • All tests pass deno test
  • All doctests pass deno test --doc

Optionally

  • Benchmarks (when introduced) do not cause major performance regressions

Array 2.0

Standard Module Implementation for array.ts

  • Add file block comment at top of file describing the adt/structure.
  • Check for any missing combinators/derivations that could be implemented.
  • Modify all combinator variable names to be readable (use style guide).
  • Add documentation to every export (and // comment local tools).
  • Check that every function and line is covered by tests in the testing directory.

Style Guide

  • Use A -> I, B -> J, C -> K, D -> L for inner types.
  • Use ua, va to match URI, VRI, etc in generic positions.
  • Implement algebraic structure types (type classes) out to length 4.
  • Use functions over const arrow functions for root exports.
  • Document all type exports.
  • Order file exports by group and then alphabetically. Groups are Types, Constructors, Combinators, Type Class Instances, Type Class Constructors, and Derived Combinators.

Documentation Guide

  • Start with at least one full sentence describing the use case for the export.
  • Include a @SInCE tag (default to 2.0.0)
  • Include an @example tag with a simple example
    • Imports should be relative for functional stuff and otherwise full urls with versions
    • Example should end with at least one assert from the deno std lib

Extras

  • Array should be renamed as readonlyarray.ts
  • Combinators for to/from readonlyarray.ts would be useful.

Either 2.0

Standard Module Implementation for either.ts

  • Add file block comment at top of file describing the adt/structure.
  • Check for any missing combinators/derivations that could be implemented.
  • Modify all combinator variable names to be readable (use style guide).
  • Add documentation to every export (and // comment local tools).
  • Check that every function and line is covered by tests in the testing directory.

Style Guide

  • Use A -> I, B -> J, C -> K, D -> L for inner types.
  • Use ua, va to match URI, VRI, etc in generic positions.
  • Implement algebraic structure types (type classes) out to length 4.
  • Use functions over const arrow functions for root exports.
  • Document all type exports.
  • Order file exports by group and then alphabetically. Groups are Types, Constructors, Combinators, Type Class Instances, Type Class Constructors, and Derived Combinators.

Documentation Guide

  • Start with at least one full sentence describing the use case for the export.
  • Include a @SInCE tag (default to 2.0.0)
  • Include an @example tag with a simple example
    • Imports should be relative for functional stuff and otherwise full urls with versions
    • Example should end with at least one assert from the deno std lib

Chain 2.0

Standard Module Implementation for chain.ts

  • Add file block comment at top of file describing the adt/structure.
  • Check for any missing combinators/derivations that could be implemented.
  • Modify all combinator variable names to be readable (use style guide).
  • Add documentation to every export (and // comment local tools).
  • Check that every function and line is covered by tests in the testing directory.

Style Guide

  • Use A -> I, B -> J, C -> K, D -> L for inner types.
  • Use ua, va to match URI, VRI, etc in generic positions.
  • Implement algebraic structure types (type classes) out to length 4.
  • Use functions over const arrow functions for root exports.
  • Document all type exports.
  • Order file exports by group and then alphabetically. Groups are Types, Constructors, Combinators, Type Class Instances, Type Class Constructors, and Derived Combinators.

Documentation Guide

  • Start with at least one full sentence describing the use case for the export.
  • Include a @SInCE tag (default to 2.0.0)
  • Include an @example tag with a simple example
    • Imports should be relative for functional stuff and otherwise full urls with versions
    • Example should end with at least one assert from the deno std lib

Bifunctor 2.0

Standard Module Implementation for bifunctor.ts

  • Add file block comment at top of file describing the adt/structure.
  • Check for any missing combinators/derivations that could be implemented.
  • Modify all combinator variable names to be readable (use style guide).
  • Add documentation to every export (and // comment local tools).
  • Check that every function and line is covered by tests in the testing directory.

Style Guide

  • Use A -> I, B -> J, C -> K, D -> L for inner types.
  • Use ua, va to match URI, VRI, etc in generic positions.
  • Implement algebraic structure types (type classes) out to length 4.
  • Use functions over const arrow functions for root exports.
  • Document all type exports.
  • Order file exports by group and then alphabetically. Groups are Types, Constructors, Combinators, Type Class Instances, Type Class Constructors, and Derived Combinators.

Documentation Guide

  • Start with at least one full sentence describing the use case for the export.
  • Include a @SInCE tag (default to 2.0.0)
  • Include an @example tag with a simple example
    • Imports should be relative for functional stuff and otherwise full urls with versions
    • Example should end with at least one assert from the deno std lib

Implement Streams

This issue is for tracking the development of a stream data type. This will likely take a fair amount of trial and error before the structure is solidified. To start off I expect I'll do the following:

I'm hoping that implementing each of these in the context of fun (without classes) will give me a solid idea of how I want streams to look. Each of these approaches has pros and cons:

most

Most is a highly performant declarative stream library.

Pros

  • Very performant
  • Uses a modular architecture
  • Already has types broken out
  • Supports imperative cancellation

Cons

  • Relies on internal private state managed by classes
  • Only supports push steams

observable

The observable spec is a simplified version of the Observer pattern implemented in javascript.

Pros

  • Likely the most familiar stream-ish interface in javascript
  • Supports imperative cancellation
  • Easy to implement

Cons

  • The canonical implementation (rxjs) primarily uses classes
  • Only supports push streams

callbags

Pros

  • Simple function implementation
  • Handles push and pull based streams
  • Has simple types

Cons

  • Less well known?

Category 2.0

Standard Module Implementation for category.ts

  • Add file block comment at top of file describing the adt/structure.
  • Check for any missing combinators/derivations that could be implemented.
  • Modify all combinator variable names to be readable (use style guide).
  • Add documentation to every export (and // comment local tools).
  • Check that every function and line is covered by tests in the testing directory.

Style Guide

  • Use A -> I, B -> J, C -> K, D -> L for inner types.
  • Use ua, va to match URI, VRI, etc in generic positions.
  • Implement algebraic structure types (type classes) out to length 4.
  • Use functions over const arrow functions for root exports.
  • Document all type exports.
  • Order file exports by group and then alphabetically. Groups are Types, Constructors, Combinators, Type Class Instances, Type Class Constructors, and Derived Combinators.

Documentation Guide

  • Start with at least one full sentence describing the use case for the export.
  • Include a @SInCE tag (default to 2.0.0)
  • Include an @example tag with a simple example
    • Imports should be relative for functional stuff and otherwise full urls with versions
    • Example should end with at least one assert from the deno std lib

Move away from widen functions

Remove widen from the library and make the default exports for all ADTs widen where appropriate. The canonical algebraic structure exports will have tighter types.

Running things.

It's quite recent that deno has introduced task runner as a thing.

My first impression is that it's more convenient than remembering a bunch of commands, but it gets quite clunky, see example below that I have for functional.

{
  "tasks": {
    "build": "deno task clean && mkdir -p out/deno && deno bundle mod.ts > out/deno/lib.js",
    "build:npm": "deno run -A --unstable ./scripts/build_node.ts $(cat VERSION)",
    "coverage": "deno coverage --unstable ./coverage --lcov > coverage/lcov.info && genhtml coverage/lcov.info  --output-directory coverage",
    "clean": "rm -rf out coverage",
    "test": "deno task clean && deno test -A --unstable --coverage=coverage",
    "version": "/Users/davidpeter/Workspace/kittenpilez/semantic-release-tooling/out/srt-aarch64-apple-darwin"
  }
}

In contrast I also have made a makefile that I've defaulted to using for deno projects which I do find cleaner, along the lines of

.PHONY: coverage
run_flags = -A --unstable
cov_dir = coverage
cov_file = ./$(cov_dir)/lcov.info

build:
	rm -rf out/deno 
	mkdir -p out/deno 
	deno bundle mod.ts > out/deno/lib.js

build-node:
	deno run $(run_flags) ./scripts/build_node.ts $(cat VERSION)

coverage:
	deno coverage --unstable ./$(cov_dir) --lcov > $(cov_file)
	genhtml $(cov_file) --output-directory $(cov_dir)

test:
	rm -rf $(cov_dir)
	deno test $(run_flags) --coverage=$(cov_dir) testing

However makefiles were not really made for running tasks.
Wonder which one you prefer.

Alternative 2.0

Standard Module Implementation for alternative.ts

  • Add file block comment at top of file describing the adt/structure.
  • Check for any missing combinators/derivations that could be implemented.
  • Modify all combinator variable names to be readable (use style guide).
  • Add documentation to every export (and // comment local tools).
  • Check that every function and line is covered by tests in the testing directory.

Style Guide

  • Use A -> I, B -> J, C -> K, D -> L for inner types.
  • Use ua, va to match URI, VRI, etc in generic positions.
  • Implement algebraic structure types (type classes) out to length 4.
  • Use functions over const arrow functions for root exports.
  • Document all type exports.
  • Order file exports by group and then alphabetically. Groups are Types, Constructors, Combinators, Type Class Instances, Type Class Constructors, and Derived Combinators.

Documentation Guide

  • Start with at least one full sentence describing the use case for the export.
  • Include a @SInCE tag (default to 2.0.0)
  • Include an @example tag with a simple example
    • Imports should be relative for functional stuff and otherwise full urls with versions
    • Example should end with at least one assert from the deno std lib

Complete README

Should contain:

  1. Description of the project
  2. History of the project
  3. Some badges around testing and build status
  4. List of documentation links
  5. Major version notes
  6. Contribution guidelines

Decoder 2.0

Standard Module Implementation for decoder.ts

  • Add file block comment at top of file describing the adt/structure.
  • Check for any missing combinators/derivations that could be implemented.
  • Modify all combinator variable names to be readable (use style guide).
  • Add documentation to every export (and // comment local tools).
  • Check that every function and line is covered by tests in the testing directory.

Style Guide

  • Use A -> I, B -> J, C -> K, D -> L for inner types.
  • Use ua, va to match URI, VRI, etc in generic positions.
  • Implement algebraic structure types (type classes) out to length 4.
  • Use functions over const arrow functions for root exports.
  • Document all type exports.
  • Order file exports by group and then alphabetically. Groups are Types, Constructors, Combinators, Type Class Instances, Type Class Constructors, and Derived Combinators.

Documentation Guide

  • Start with at least one full sentence describing the use case for the export.
  • Include a @SInCE tag (default to 2.0.0)
  • Include an @example tag with a simple example
    • Imports should be relative for functional stuff and otherwise full urls with versions
    • Example should end with at least one assert from the deno std lib

Document every function with an example

  1. All function or const arrow exports should have a short description and an example of use.
  2. Don't bother turning const exports into function exports as we are going to tackle that in deno_doc over the summer
  3. Document all algebraic structures with text meant to help the reader build an intuition for the structure.

Following are the files that need docs:

  • Affect
  • Array
  • Const
  • Datum
  • Derivations
  • Either
  • Functions
  • HKT
  • Identity
  • IO
  • IOEither
  • Map
  • Monoid
  • Nilable
  • Option
  • Ord
  • Reader
  • ReaderEither
  • Record
  • Semigroup
  • Sequence
  • Set
  • Setoid
  • State
  • Task
  • TaskEither
  • These
  • Tree
  • Type Classes
  • Types
  • At
  • Index
  • Iso
  • Lens
  • Optional
  • Prism
  • Traversal
  • Decoder
  • Guard
  • Schemable

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.