Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New Method! #12

Open
SimonMeskens opened this issue Apr 21, 2019 · 8 comments
Open

New Method! #12

SimonMeskens opened this issue Apr 21, 2019 · 8 comments

Comments

@SimonMeskens
Copy link
Owner

SimonMeskens commented Apr 21, 2019

@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
Copy link
Owner Author

SimonMeskens commented Apr 21, 2019

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
Copy link
Owner Author

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"];

@SimonMeskens SimonMeskens changed the title HKTs in TypeScript 3.50! New Method! Apr 21, 2019
@KiaraGrouwstra
Copy link

link to the issue?

@SimonMeskens
Copy link
Owner Author

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
Copy link

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! 😄

@SimonMeskens
Copy link
Owner Author

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
Copy link

nadameu commented Apr 25, 2019

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
Copy link
Owner Author

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"];

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

No branches or pull requests

3 participants