Skip to content

Conversation

@pixeleet
Copy link
Member

@pixeleet pixeleet commented May 29, 2023

Implements the Do/bind syntax for fun

@pixeleet pixeleet mentioned this pull request May 29, 2023
11 tasks
@pixeleet pixeleet force-pushed the feat/do-notation branch from 9e1ab00 to b039bbe Compare May 29, 2023 13:10
@pixeleet pixeleet linked an issue Jul 20, 2023 that may be closed by this pull request
11 tasks
@baetheus
Copy link

So, I guess your question is why isn't the bind type definition working. The implementation in this PR is this:

export function bind<U extends Kind>(M: Chain<U>):
  <N extends string, A, B, C, I, J = never, K = never, D = unknown, E = unknown>
  (name: Exclude<N, keyof A>, f: (a: A) => $<U, [I, J, K], [D], [E]>) =>
  <B, C>
  (ma: $<U, [A, B, C], [D], [E]>) =>
  $<U, [{ readonly [K in keyof A | N]: K extends keyof A ? A[K] : I }], [D], [E]>
{
    return (name, f) =>
      // @ts-expect-error <type-errors> TODO: @baetheus
      M.chain((a) => M.map((b) => Object.assign({}, a, { [name]: b }) as any)(f(a))
    );
}

The first problem I see is that there are several shadowed generics. The first curried function declares N, A, B, C, D, E, I, J, and K. But it only uses N, A, I, J, K, D, and E, and doesn't use B or C. Then B and C are declared generic again but never used in the final output.. so they are effectively lost. So those are the problems I see. Following is how I think of bind in fun and ts..

  1. It should take an inner A object (aka a struct/dictionary/etc) and allow the developer to return a new adt at to be stored at name. So the first part is name and A => U<I>, or to be more verbose (a: A) => $<U, [I, J, K], [L], [M]>.
  2. It should take data last, which is an adt that contains an A. U<A> or $<U, [A, B, C], [D], [E]>.
  3. Lastly, it should chain the A out of 2 and into 1, combining the other type substitutions appropriately, ultimately returning $<U, [A & { [name]: I, B | J, C | K], [D & L], [E & M]>.

To illustrate I've pasted the new flatmappable.ts file that I've been working on.

import type { $, Kind } from "./kind.ts";
import type { Wrappable } from "./wrappable.ts";
import type { Mappable } from "./mappable.ts";

import { pipe } from "./fn.ts";

/**
 * A Flatmappable structure.
 */
export type Flatmappable<U extends Kind> = {
  readonly flatmap: <A, I, J = never, K = never, L = unknown, M = unknown>(
    fati: (a: A) => $<U, [I, J, K], [L], [M]>,
  ) => <B = never, C = never, D = unknown, E = unknown>(
    ta: $<U, [A, B, C], [D], [E]>,
  ) => $<U, [I, B | J, C | K], [D & L], [E & M]>;
};

/**
 * Create a tap function for a structure with instances of Wrappable and
 * Flatmappable. A tap function allows one to break out of the functional
 * codeflow. It is generally not advised to use tap for code flow but to
 * consider an escape hatch to do things like tracing or logging.
 *
 * @since 2.0.0
 */
export function createTap<U extends Kind>(
  { wrap, flatmap }: Wrappable<U> & Flatmappable<U>,
): <A>(
  fn: (value: A) => void,
) => <B = never, C = never, D = unknown, E = unknown>(
  ua: $<U, [A, B, C], [D], [E]>,
) => $<U, [A, B, C], [D], [E]> {
  return (fn) =>
    flatmap((a) => {
      fn(a);
      return wrap(a);
    });
}

/**
 * Create a bind function for a structure with instances of Mappable and
 * Flatmappable. A bind function allows one to flatmap into named fields in a
 * struct, collecting values from the result of the flatmap in the order that
 * they are completed.
 *
 * @since 2.0.0
 */
export function createBind<U extends Kind>(
  { flatmap, map }: Flatmappable<U> & Mappable<U>,
): <N extends string, A, I, J = never, K = never, L = unknown, M = unknown>(
  name: Exclude<N, keyof A>,
  faui: (a: A) => $<U, [I, J, K], [L], [M]>,
) => <B = never, C = never, D = unknown, E = unknown>(
  ua: $<U, [A, B, C], [D], [E]>,
) => $<
  U,
  [{ readonly [K in keyof A | N]: K extends keyof A ? A[K] : I }, B | J, C | K],
  [D & L],
  [E & M]
> {
  return (name, fai) =>
    flatmap((a) =>
      pipe(
        fai(a),
        // deno-lint-ignore no-explicit-any
        map((b) => Object.assign({}, a, { [name]: b }) as any),
      )
    );
}

@pixeleet
Copy link
Member Author

/**
 * Create a bind function for a structure with instances of Mappable and
 * Flatmappable. A bind function allows one to flatmap into named fields in a
 * struct, collecting values from the result of the flatmap in the order that
 * they are completed.
 *
 * @since 2.0.0
 */
export function createBind<U extends Kind>(
  { flatmap, map }: Flatmappable<U> & Mappable<U>,
): <N extends string, A, I, J = never, K = never, L = unknown, M = unknown>(
  name: Exclude<N, keyof A>,
  faui: (a: A) => $<U, [I, J, K], [L], [M]>,
) => <B = never, C = never, D = unknown, E = unknown>(
  ua: $<U, [A, B, C], [D], [E]>,
) => $<
  U,
  [{ readonly [K in keyof A | N]: K extends keyof A ? A[K] : I }, B | J, C | K],
  [D & L],
  [E & M]
> {
  return (name, fai) =>
    flatmap((a) =>
      pipe(
        fai(a),
        // deno-lint-ignore no-explicit-any
        map((b) => Object.assign({}, a, { [name]: b }) as any),
      )
    );
}

Refactor looks real neat!

@baetheus
Copy link

It's pretty much the same, I think.. Just no interface for Do, Bind, and BindTo

@baetheus
Copy link

I think I just noticed an issue with tap though. One might lose information from an adt like Datum.

@baetheus
Copy link

Noo.. wait, maybe not..

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Do/bind Syntax

3 participants