SimonMeskens / TypeProps

Small library for describing HKTs in TypeScript
Apache License 2.0
32 stars 2 forks source link

New Method! #12

Open SimonMeskens opened 5 years ago

SimonMeskens commented 5 years ago

@tycho01 @nadameu @masaeedu

TypeProps will probably be coming back, HKTs just became trivial:

// Functor
interface StaticFunctor<P extends TypeProp> {
    map<T, U>(transform: (a: T) => U, mappable: Of<P, [T]>): Of<P, [U]>;
}

// Prop
interface ArrayProp extends TypeProp {
    params: this["args"][0] extends Array<infer T> ? [T] : never;
    type: this["args"][0][];
}

// Examples
const arrayFunctor: StaticFunctor<ArrayProp> = {
    map: <A, B>(fn: (a: A) => B, fa: A[]): B[] => {
        return fa.map(fn);
    }
};

// TypeProps Library

interface TypeProp {
    args: any[];
    params: unknown[];
    type: unknown;
}

type From<Prop extends TypeProp, T = Of<Prop>> = (Prop & { args: [T] })["params"];
type Of<Prop extends TypeProp, T extends unknown[] = unknown[]> = (Prop & {
    args: T;
})["type"];

Big thanks to @strax for the new insight. Unfortunately there's a bug in TS 3.4.4 that stops us from using it right now in typescript@latest

SimonMeskens commented 5 years ago

Fair warning: there's still a few edge case bugs in that sample, it's still very early POC.

const arrayFunctor: StaticFunctor<ArrayProp> = {
    map: <A, B>(fn: (a: A) => B, fa: A): B[] => {
        return fa.map(fn);
    }
};

For example, this doesn't give an error, but it should, since fa is not an array

Reproducible problem:

const problem: {
    map<F extends any[], U>(
        transform: (a: (typeof mappable extends Array<infer T> ? T[] : never)[0]) => U,
        mappable: F
    ): U[];
} = {
    map: <A, B>(fn: (a: A) => B, fa: A): B[] => {
        return fa.map(fn);
    }
};
SimonMeskens commented 5 years ago

Sorry for the slight spam, but I know people read these issues through email, the issue was with covariance, it's fixed now. Note that this also works in 3.4.3 (needed any[] instead of unknown[] in TypeProp) and probably pretty far back actually!

Sample:

// Functor
interface StaticFunctor<P extends TypeProp> {
    map<T, U>(transform: (a: T) => U, mappable: Of<P, [T]>): Of<P, [U]>;
}

// Prop
interface ArrayProp extends TypeProp {
    params: this["args"][0] extends Array<infer T> ? [T] : never;
    type: this["args"][0][];
}

// Examples
const arrayFunctor: StaticFunctor<ArrayProp> = {
    map: <A, B>(fn: (a: A) => B, fa: A[]): B[] => {
        return fa.map(fn);
    }
};

// TypeProps Library
interface TypeProp {
    args: any[];
    params: unknown[];
    type: unknown;
}

type From<Prop extends TypeProp, T = Of<Prop>> = (Prop & { args: [T] })["params"];
type Of<Prop extends TypeProp, T extends unknown[] = unknown[]> = (Prop & {
    args: T;
})["type"];
KiaraGrouwstra commented 5 years ago

link to the issue?

SimonMeskens commented 5 years ago

I'm not sure what issue you refer to, there's several that were mentioned in this issue. I originally assumed this new technique would only work in 3.5, due to a weird edge case, but I found a fix already.

The core idea that strax showed me though, is just that you can do this:

interface TypeFunction {
    arguments: unknown[];
    result: unknown;
}

interface ToArray extends TypeFunction {
    result: Array<this["arguments"][0]>;
}

type ForEach<T, Mapper extends TypeFunction> = (Mapper & { arguments: [T] })["result"];

// type Test = number[]
type Test = ForEach<number, ToArray>;

I think this is a novel idea that I haven't seen yet, that allows arbitrary type-level function execution

KiaraGrouwstra commented 5 years ago

So if I understand correctly this is essentially like type-level function manipulation, although in my understanding this doesn't directly convert to/from functions yet in a generics-preserving manner yet. If we'd have that bit down as well, I guess that'd also address reduce/map/whatever.

But if we got HKTs already that's actually already pretty big! I imagine @gcanti is gonna be pretty happy! :smile:

SimonMeskens commented 5 years ago

I've been looking at it and it's in some ways a massive improvement over the previous methods, but it doesn't seem to fix the one big problem in that it will still need every project to agree on using the same specific method of doing HKTs. In that sense, I'm not sure if fp-ts for example will benefit all that much. It allows Gcanti to get rid of having to decorate every HKT with a certain symbol though.

I was hoping to find a way to make it so, for example Ramda could seamlessly support any HKT library, but I don't think we're there yet. Exciting progress though :)

I'm still experimenting with what this new system can do, but it does seem like we're still not able to fully convert from/to/apply generic functions.

nadameu commented 5 years ago

When defining arrayFunctor, there is no need for manual types on the function, because of the type annotation. The following is correctly inferred by TypeScript:

const arrayFunctor: StaticFunctor<ArrayProp> = {
    map: (fn, fa) => {
        return fa.map(fn);
    } // Inferred as `<T, U>(transform: (a: T) => U, mappable: T[]): U[]`
};

Also, the StaticFunctor example does not yet work on type constructors with more than one type parameter, e.g. Either.

SimonMeskens commented 5 years ago

Yeah, I have a bunch of different versions, it wasn't meant to be the definitive example yet.

For now, I'm just testing out a bunch of things, like this:

type Test1 = λ<Not, [True]>;        // False
type Test2 = λ<And, [True, False]>; // False
type Test3 = λ<And, [True, True]>;  // True

// Boolean

interface True extends Func {
    expression: Var<this, 0>;
}

interface False extends Func {
    expression: Var<this, 1>;
}

interface Not extends Func {
    expression: λ<Var<this, 0>, [False, True]>
}

interface And extends Func {
    expression: λ<Var<this, 0>, [Var<this, 1>, Var<this, 0>]>
}

// Plumbing

type Func = {
    variables: Func[];
    expression: unknown;
}

type Var<F extends Func, X extends number> = F["variables"][X];

type λ<Exp extends Func, Vars extends unknown[]> = (Exp & {
    variables: Vars;
})["expression"];