Skip to content

Are there recommendations for binding type-level functions to runtime implementations ? #3

@GautierBlandin

Description

@GautierBlandin

The library's documentation showcases a very elegant type-level function pipeline:

interface Append<Suffix extends string> extends TypeLambda<[s: string], string> {
  return: `${Arg0<this>}${Suffix}`;
}

type ConcatNames = Flow<
  Filter<Flow<StringLength, NotExtend<1 | 2>>>,
  Take<3>,
  Map<CapitalizeString>,
  JoinBy<", ">,
  Append<", ...">
>;

type Names = ["alice", "bob", "i", "charlie", "david"];
type _ = Call1<ConcatNames, Names>; // => "Alice, Bob, Charlie, ..."

After experimenting with the lib, a common need that I ended up having is the ability to bind type-lambdas to a runtime implementation, and to be able to compose them together.

I have made a primitive attempt using the following approach:

export interface TypeLambdaFn<
  L extends TypeLambda1,
> extends TypeLambdaFnBrand<L> {
  <const A>(value: A): Call1<L, A>;
}

A common pattern I have encountered is the definition of type-lambda functions that both accept another type-lambda function as input, and return a type-lambda function as output.

Example application with a tag-based dependent-typing library:

// dependent.ts
export const flowUniform = <F extends TypeLambdaFn<any>>(
  f: F,
): TypeLambdaFn<FlowUniformLambda<ExtractLambda<F>>> => { ... }

// consumer.ts
const evolve = A.fn((_a: A) =>
      Dep.match({ B: (_b: B) => 'A-B' as const, C: (_c: C) => 'A-C' as const }),
    );

const mapped = pipe(evolve, Dep.flowUniform(Dep.flowUniform(Result.okL)));

typeof evolve (inferred):

const evolve: Dep.DepFn<{
  A: {
    in: [A]
    out: Dep.DepFn<{
      B: {
        in: [_b: B]
        out: 'A-B'
      }
      C: {
        in: [_c: C]
        out: 'A-C'
      }
    }>
  }
}>

typeof mapped (inferred):
const mapped: Dep.DepFn<{
  A: {
    in: [A]
    out: Dep.DepFn<{
      B: {
        in: [_b: B]
        out: Result.Result<'A-B', never>
      }
      C: {
        in: [_c: C]
        out: Result.Result<'A-C', never>
      }
    }>
  }
}>

I was wondering if you had experimented with approaches around the interaction of type-level functions and runtime functions. I find the ability to write properly generic runtime functions (that is, generic function that correctly compose with other generic functions) extremely compelling.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions