SimonMeskens / TypeProps

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

Monads over multiple parameters #3

Open SimonMeskens opened 6 years ago

SimonMeskens commented 6 years ago

@nadameu

TypeProp-esque Monad example%3A%20Generic%3CF%2C%20B%3E%3B%0D%0A%7D%0D%0A%0D%0Ainterface%20Monad%3CF%20extends%20RegisteredTypes%2C%20A%3E%20extends%20Functor%3CF%2C%20A%3E%20%7B%0D%0A%20%20chain%3CB%3E(f%3A%20(_%3A%20A)%20%3D%3E%20Generic%3CF%2C%20B%3E)%3A%20Generic%3CF%2C%20B%3E%3B%0D%0A%7D%0D%0A%0D%0A%2F%20Identity%20%2F%0D%0Aclass%20Identity%3CA%20%3D%20any%3E%20implements%20Monad%3CIdentity%2C%20A%3E%20%7B%0D%0A%20%20constructor(private%20readonly%20value%3A%20A)%20%7B%7D%0D%0A%0D%0A%20%20chain%3CB%3E(f%3A%20(%3A%20A)%20%3D%3E%20Identity%3CB%3E)%3A%20Identity%3CB%3E%20%7B%0D%0A%20%20%20%20return%20f(this.value)%3B%0D%0A%20%20%7D%0D%0A%20%20fold%3CB%3E(f%3A%20(%3A%20A)%20%3D%3E%20B)%3A%20B%20%7B%0D%0A%20%20%20%20return%20f(this.value)%3B%0D%0A%20%20%7D%0D%0A%20%20map%3CB%3E(f%3A%20(%3A%20A)%20%3D%3E%20B)%3A%20Identity%3CB%3E%20%7B%0D%0A%20%20%20%20return%20new%20Identity(f(this.value))%3B%0D%0A%20%20%7D%0D%0A%7D%0D%0Aconst%20identity%20%3D%20%3CA%3E(a%3A%20A)%3A%20Identity%3CA%3E%20%3D%3E%20new%20Identity(a)%3B%0D%0A%0D%0Ainterface%20TypesDictionary%3CF%2C%20A%20%3D%20never%3E%20%7B%0D%0A%20%20Identity%3A%20%7B%0D%0A%20%20%20%20param%3A%20F%20extends%20Identity%3Cinfer%20A%3E%20%3F%20A%20%3A%20never%3B%0D%0A%20%20%20%20type%3A%20F%20extends%20Identity%20%3F%20Identity%3CA%3E%20%3A%20never%3B%0D%0A%20%20%7D%3B%0D%0A%7D%0D%0A%0D%0A%2F%20Maybe%20%2F%0D%0Aabstract%20class%20Maybe%3CA%20%3D%20any%3E%20implements%20Monad%3CMaybe%2C%20A%3E%20%7B%0D%0A%20%20abstract%20maybe%3CB%3E(b%3A%20B%2C%20f%3A%20(%3A%20A)%20%3D%3E%20B)%3A%20B%3B%0D%0A%20%20chain%3CB%3E(f%3A%20(%3A%20A)%20%3D%3E%20Maybe%3CB%3E)%3A%20Maybe%3CB%3E%20%7B%0D%0A%20%20%20%20return%20this.maybe(nothing%2C%20f)%3B%0D%0A%20%20%7D%0D%0A%20%20map%3CB%3E(f%3A%20(%3A%20A)%20%3D%3E%20B)%3A%20Maybe%3CB%3E%20%7B%0D%0A%20%20%20%20return%20this.maybe(nothing%2C%20x%20%3D%3E%20just(f(x)))%3B%0D%0A%20%20%7D%0D%0A%7D%0D%0A%0D%0Aclass%20Just%3CA%3E%20extends%20Maybe%3CA%3E%20%7B%0D%0A%20%20constructor(private%20readonly%20value%3A%20A)%20%7B%0D%0A%20%20%20%20super()%3B%0D%0A%20%20%7D%0D%0A%20%20maybe(%2C%20f)%20%7B%0D%0A%20%20%20%20return%20f(this.value)%3B%0D%0A%20%20%7D%0D%0A%7D%0D%0Aconst%20just%20%3D%20%3CA%3E(a%3A%20A)%3A%20Maybe%3CA%3E%20%3D%3E%20new%20Just(a)%3B%0D%0A%0D%0Aclass%20Nothing%20extends%20Maybe%3Cnever%3E%20%7B%0D%0A%20%20maybe(b)%20%7B%0D%0A%20%20%20%20return%20b%3B%0D%0A%20%20%7D%0D%0A%7D%0D%0Aconst%20nothing%3A%20Maybe%3Cnever%3E%20%3D%20new%20Nothing()%3B%0D%0A%0D%0Ainterface%20TypesDictionary%3CF%2C%20A%20%3D%20never%3E%20%7B%0D%0A%20%20Maybe%3A%20%7B%0D%0A%20%20%20%20param%3A%20F%20extends%20Maybe%3Cinfer%20A%3E%20%3F%20A%20%3A%20never%3B%0D%0A%20%20%20%20type%3A%20F%20extends%20Maybe%20%3F%20Maybe%3CA%3E%20%3A%20never%3B%0D%0A%20%20%7D%3B%0D%0A%7D%0D%0A%0D%0A%2F%20Either%20%2F%0D%0Aabstract%20class%20Either%3CL%20%3D%20any%2C%20R%20%3D%20any%3E%20implements%20Monad%3CEither%3CL%3E%2C%20R%3E%20%7B%0D%0A%20%20abstract%20either%3CB%3E(f%3A%20(%3A%20L)%20%3D%3E%20B%2C%20g%3A%20(%3A%20R)%20%3D%3E%20B)%3A%20B%3B%0D%0A%0D%0A%20%20chain%3CB%3E(f%3A%20(%3A%20R)%20%3D%3E%20Either%3CL%2C%20B%3E)%3A%20Either%3CL%2C%20B%3E%20%7B%0D%0A%20%20%20%20return%20this.either(left%2C%20f)%3B%0D%0A%20%20%7D%0D%0A%20%20map%3CB%3E(f%3A%20(_%3A%20R)%20%3D%3E%20B)%3A%20Either%3CL%2C%20B%3E%20%7B%0D%0A%20%20%20%20return%20this.either%3CEither%3CL%2C%20B%3E%3E(left%2C%20x%20%3D%3E%20right(f(x)))%3B%0D%0A%20%20%7D%0D%0A%7D%0D%0A%0D%0Aclass%20Left%3CA%3E%20extends%20Either%3CA%2C%20never%3E%20%7B%0D%0A%20%20constructor(private%20readonly%20_value%3A%20A)%20%7B%0D%0A%20%20%20%20super()%3B%0D%0A%20%20%7D%0D%0A%20%20either(f)%20%7B%0D%0A%20%20%20%20return%20f(this._value)%3B%0D%0A%20%20%7D%0D%0A%7D%0D%0Aconst%20left%20%3D%20%3CA%3E(a%3A%20A)%3A%20Either%3CA%2C%20never%3E%20%3D%3E%20new%20Left(a)%3B%0D%0A%0D%0Aclass%20Right%3CA%3E%20extends%20Either%3Cnever%2C%20A%3E%20%7B%0D%0A%20%20constructor(private%20readonly%20value%3A%20A)%20%7B%0D%0A%20%20%20%20super()%3B%0D%0A%20%20%7D%0D%0A%20%20either(%2C%20g)%20%7B%0D%0A%20%20%20%20return%20g(this.value)%3B%0D%0A%20%20%7D%0D%0A%7D%0D%0Aconst%20right%20%3D%20%3CA%3E(a%3A%20A)%3A%20Either%3Cnever%2C%20A%3E%20%3D%3E%20new%20Right(a)%3B%0D%0A%0D%0Ainterface%20TypesDictionary%3CF%2C%20A%20%3D%20never%3E%20%7B%0D%0A%20%20Either%3A%20%7B%0D%0A%20%20%20%20param%3A%20F%20extends%20Either%3Cany%2C%20infer%20R%3E%20%3F%20R%20%3A%20never%3B%0D%0A%20%20%20%20type%3A%20F%20extends%20Either%3Cinfer%20L%3E%20%3F%20Either%3CL%2C%20A%3E%20%3A%20never%3B%0D%0A%20%20%7D%3B%0D%0A%7D%0D%0A%0D%0A%2F%20Static%20methods%20%2F%0D%0Aconst%20chain%20%3D%20%3C%0D%0A%20%20F%20extends%20RegisteredTypes%20%26%20Monad%3CF%2C%20A%3E%2C%0D%0A%20%20A%20extends%20TypeParamOf%3CF%3E%2C%0D%0A%20%20B%0D%0A%3E(%0D%0A%20%20f%3A%20(%3A%20A)%20%3D%3E%20Monad%3CF%2C%20B%3E%2C%0D%0A%20%20fa%3A%20F%0D%0A)%3A%20Generic%3CF%2C%20B%3E%20%3D%3E%20fa.chain(f%20as%20any)%3B%0D%0Aconst%20map%20%3D%20%3C%0D%0A%20%20F%20extends%20RegisteredTypes%20%26%20Functor%3CF%2C%20A%3E%2C%0D%0A%20%20A%20extends%20TypeParamOf%3CF%3E%2C%0D%0A%20%20B%0D%0A%3E(%0D%0A%20%20f%3A%20(_%3A%20A)%20%3D%3E%20B%2C%0D%0A%20%20fa%3A%20F%0D%0A)%3A%20Generic%3CF%2C%20B%3E%20%3D%3E%20fa.map(f)%3B%0D%0A%0D%0A%2F%20Examples%20%2F%0D%0Aconst%20x%20%3D%20right(%22Hello%20World%22)%20as%20Either%3Cnumber%2C%20string%3E%3B%0D%0Aconst%20y%20%3D%20map(val%20%3D%3E%20val.length%2C%20x)%3B%0D%0Aconst%20z%20%3D%20chain(val%20%3D%3E%20(val%20%3C%205%20%3F%20right(val)%20%3A%20left(val))%2C%20y)%3B%0D%0A)

Okay, so I was thinking about your example code and how Haskell handles monads over types with multiple parameters and I think the solution is to use a little bit of your method and mine.

A TypeProp is an object that has a way to get a list of parameters and a way to construct a type, given parameters. This might change in the future, I have a few ideas in the pipeline that might define TypeProps differently. One thing is that this library is basically a dynamic, type-level pattern matcher. There's currently some TypeScript issues blocking it from allowing me to expose that behavior, but that might change things.

Now, here's how Haskell defines the Either monad:

instance Monad (Either e) where
    Left  l >>= _ = Left l
    Right r >>= k = k r

As you can see, it basically it creates a new TypeProp specifically for Monads, telling it how to infer parameters, like your example does. So now I'm thinking that the Monad interface's parameter shouldn't be a type, but a TypeProp. So far, so good, what's missing is a way to transform TypeProps, that is, you should be able to create a TypeProp from another TypeProp, telling it which parameters to infer and which ones to fill in.

I'll be working on this next. Thoughts?

nadameu commented 6 years ago

Haskell's implementation was exactly my inspiration for the code I shared.

I will give some thought into how to implement your idea for the next iteration of the library.

SimonMeskens commented 6 years ago

Yeah, your example and looking at Haskell's libraries gave me the right idea. I think I know how to do it too. I've already changed the TypeProps library to be a lot better thanks to your idea. I'll share my results hopefully tonight.

SimonMeskens commented 6 years ago

The problem with storing a TypeProp anywhere is that I'm currently blocked by a few restrictions in TypeScript, making it so only the global type dictionary works. The issue is this:

<T extends any>() => {
    infer: T extends Array<infer U> ? [U] : never
    construct: Array<T[0]>
}

This is a dynamic TypeProp, but there's no way to infer the return type right now. I can sidestep the issue, but until we get some way in the language to infer generic function types, that construct will never work. It's this same construct that I could use to generalize TypeProps to a full-blown pattern matcher.

nadameu commented 6 years ago

I don't know if this is exactly what you're looking for:

Setup

Let's create a few placeholders.

const _A = Symbol('placehoderA');
const _B = Symbol('placehoderB');
type _A = typeof _A;
type _B = typeof _B;

We can use these placeholders to represent a parameter that might vary.

For instance:

interface Bifunctor<F extends RegisteredTypes, A, B> {
  bimap<C, D>(f: (_: A) => C, g: (_:B) => D): Generic<F, C, D>;
}

class Either<L, R> implements Bifunctor<Either<_A, _B>, L, R> { ... }

Most of the time there will only be one variable parameter.

So let's create a shorthand representation.

type _ = _A;

Now let's use it:

class Either<L, R> implements Monad<Either<L, _>, R> { ... }

In Haskell this would be:

instance Monad (Either e) where ...

The rest of the code is almost the same as before.

Some changes made: type is to create the RegisteredTypes set. params is to extract the params from a given instance. construct is to create an instance using the placeholders above.

interface TypesDictionary<F = any, A = never, B = never> {
  never: {
    type: never;
    params: never;
    construct: never;
  };
}

type DictKeys = keyof TypesDictionary;

type RegisteredTypes = TypesDictionary[DictKeys]['type'];

type Generic<F extends RegisteredTypes, A = never, B = never> = TypesDictionary<F, A, B>[DictKeys]['construct'];

type TypeParamsOf<F extends RegisteredTypes> = TypesDictionary<F>[DictKeys]['params'];

Creating a generic type

Ok, now for the difficult part: implementing Tuple2 and all its possible type variations.

Here it goes (WARNING: deep ternary nesting ahead):

type Tuple2<L = any, R = any> = [L, R];

interface TypesDictionary<F = any, A = never, B = never> {
  Tuple2: {
    type: Tuple2;
    params: F extends Tuple2 ?
      {
        L: F extends Tuple2<infer L, any> ? L : never;
        R: F extends Tuple2<any, infer R> ? R : never;
      } :
      {};
    construct: F extends Tuple2<_A, _B> ?
      Tuple2<A, B> :
      F extends Tuple2<_B, _A> ?
        Tuple2<B, A> :
        F extends Tuple2<_A, infer R> ?
          Tuple2<A, R> :
          F extends Tuple2<infer L, _A> ?
            Tuple2<L, A> :
            F extends Tuple2<infer L, infer R> ?
              Tuple2<L, R> :
              never;
  };
}

Now imagine having to implement Tuple5!

Testing everything

Ok, now we create some fake types just for testing

type origL = 'L';
type origR = 'R';
type nowA = 'A';
type nowB = 'B';

And put them to the test:

let fixed: Generic<Tuple2<origL, origR>>;
let varyLeft: Generic<Tuple2<_, origR>, nowA>;
let varyRight: Generic<Tuple2<origL, _>, nowA>;
let varyBoth: Generic<Tuple2<_A, _B>, nowA, nowB>;
let varyBothReversed: Generic<Tuple2<_B, _A>, nowA, nowB>;

let paramsFixed: TypeParamsOf<typeof fixed>;
let paramsVaryLeft: TypeParamsOf<typeof varyLeft>;
let paramsVaryRight: TypeParamsOf<typeof varyRight>;
let paramsVaryBoth: TypeParamsOf<typeof varyBoth>;
let paramsVaryBothReversed: TypeParamsOf<typeof varyBothReversed>;

Now open this playground and hover this last section to check if it works (spoiler alert: it does).

SimonMeskens commented 6 years ago

that's interesting and in line with some of what I'm doing, but Tuple2 is not enough, since we're dealing with dynamic Tuples of any length. Off the top of my head there's some really common 3 parameter types and I've already run into a few 4 parameter types in the GenericEither sample. And here's the thing: we don't know at call site how many type parameters the type will have either.

I actually just found a full solution, but I'm missing one primitive:

type Merge<T extends TupleLike, U extends TupleLike>

What this type does is basically the same as your deep ternary. T has placeholders and we need to replace each one in succession with one from U. It's proving hard to write though.

here's an example, where * is the placeholder type:

Merge<[1, *, 3, *, 5], [2, 4]> = [1, 2, 3, 4, 5]
nadameu commented 6 years ago

I see what you're trying to do, but may I point out, while this would work, it would only fill the placeholders in order.

Say you want to change parameters 2 and 4 at the same time, but the interface returns them in the inverse order.

One suggestion I have from watching a lot of math videos on YouTube: try to solve a simpler problem first, perhaps it will give you the tools to solve a harder one.

You might try figuring out something like this first:

Replace<[A, B, C], 1, D> = <[A, D, C]>

The signature would be Replace<TupleLike, Index, Replacement>: TupleLike

SimonMeskens commented 6 years ago

Yeah, I found an implementation for Merge, using exactly that. You are correct that the placeholders still force the order to be left to right, I'll have to add a way to do *1 *2, etc, which should be fairly straightforward.

For reference, here's the Merge:

        type Replace<T extends TupleLike, Index extends number, Element> = {
            [Key in Keys<T>]: Key extends Index ? Element : T[Key]
        };

        type Pop<T extends TupleLike> = util.Simplify<
            {
                [Key in Keys<{ length: util.Subtract<T["length"], 1> }>]: T[Key]
            } & {
                length: util.Subtract<T["length"], 1>;
            }
        >;

        type Merge<
            T extends TupleLike,
            U extends TupleLike,
            Counter extends number = util.Subtract<U["length"], 1>
        > = util.Simplify<
            ({
                [Index in util.Span<Counter>]: Counter extends 0
                    ? ReplacePlaceholder<T, U[Counter]>
                    : Merge<
                          ReplacePlaceholder<
                              T,
                              U[util.Subtract<U["length"], 1>]
                          >,
                          Pop<U>,
                          util.Subtract<Counter, 1>
                      >
            } & {
                [index: number]: never;
            })[Counter] & { length: T["length"] }
        >;

        type ReplacePlaceholder<
            T extends TupleLike,
            U,
            Counter extends number = util.Subtract<T["length"], 1>
        > = ({
            [Index in Keys<T>]: T[Index] extends _
                ? Replace<T, Index, U>
                : ReplacePlaceholder<T, U, util.Subtract<Counter, 1>>
        } & {
            [index: number]: never;
        })[Counter];

Complexity is going way up, so I'm starting to doubt where exactly this library will sit. If we had the abovementioned generic function infer, I could get rid of this whole part though, so I think for now, I'll do it this way and pray we get a resolution down the line.

Part of the existence of this library is the tuple manipulations (the other part being the pattern matcher, which I also can't generalize due to the function infer).

Maybe there's two libraries here? A really simple one that is just a slightly better mousetrap to help with fantasy-land types and the monstrosity this is turning into. I might offer both under different namespaces or even different repos.

SimonMeskens commented 6 years ago

Turns out if you don't need ordering, all the complex code goes away, go figure. Ah well, was a fun night of work.

        const Placeholder: {
            readonly 0: unique symbol;
            readonly 1: unique symbol;
            readonly 2: unique symbol;
            readonly 3: unique symbol;
            readonly 4: unique symbol;
            readonly 5: unique symbol;
            readonly 6: unique symbol;
            readonly 7: unique symbol;
            readonly 8: unique symbol;
            readonly 9: unique symbol;
        };
        type Placeholders = typeof Placeholder[keyof typeof Placeholder];

        type _<
            T extends keyof typeof Placeholder
        > = typeof Placeholder[T] & { index: T };

        type ReplacePlaceholder<T extends TupleLike, U extends TupleLike> = {
            [Index in Keys<T>]: T[Index] extends Placeholders
                ? U[T[Index]["index"]]
                : T[Index]
        };

        type Test = ReplacePlaceholder<[_<1>, _<0>, 2], [3, 4]>;
nadameu commented 6 years ago

Very clean and simple solution!

Can you give me some code example of the problem you are trying to solve with this library? I might think of another approach.

SimonMeskens commented 6 years ago

I'm in the process of finding an writing examples, but the main idea is that something like fp-ts requires you to have Generic, Generic2, Generic3, etc interfaces. My plan from the start was to store type information in tuples, so they can be manipulated. The Placeholder system you suggested is working wonders, I can now say something like [Infer, Infer, _<0>].

I guess the goals are:

I've currently proven that all of these are possible and now I'm converting the two examples given to me in #1, which are quite nice examples, as they aren't typable by fp-ts. On top of that, I'm forcing myself to use a generic Monad interface to type them, so I can make sure that use case works.

SimonMeskens commented 6 years ago

I did it! I added the new samples to the repo and I'm getting amazing typing. The examples in question use a form of Monad (Map :: (a -> b) -> T a -> T b) that doesn't lead with the monadic type, but with the operation, so the only issue is that we don't know the actual monad type until late. This means that map gives some really funky type popups, but it works fine.

Unfortunately, I can't give out Playground links until TypeScript 2.9 hits. I needed some of the new features to make it all work properly.

Here's the new TypeProps library + the Monad interface. A LOT of hours went into this, but it's worth it.

import { Generic, TypeTag, _ } from "typeprops";

export interface Monad<Tag extends TypeTag, Map extends ArrayLike<any> = [_]> {
    // Functor
    // map :: (a -> b) -> T a -> T b
    map: <T, U>(
        transform: (value: T) => U
    ) => <M extends Generic<Tag, [T], Map, any>>(
        monad: M
    ) => Generic<Tag, [U], Map, M>;

    // Monad
    // of :: a -> T a
    of: <T>(value: T) => Generic<Tag, [T], Map, never>;
    // chain :: (a -> T b) -> T a -> T b
    chain: <T, M extends Generic<Tag, any>>(
        transform: (a: T) => M
    ) => (monad: Generic<Tag, [T], Map, M>) => M;
}
declare module "typeprops" {
    type _<
        T extends keyof typeof util.Placeholder = 0
    > = typeof util.Placeholder[T] & { index: T };
    type Infer = typeof util.InferSymbol;

    interface TypeProps<T extends any = any> {
        never: {
            infer: T extends never ? { length: 0 } : never;
            construct: never;
        };
    }

    type TypeTag<T = any> = {
        [K in keyof TypeProps]: TypeProps<T>[K]["infer"] extends never
            ? never
            : K
    }[keyof TypeProps];

    type TypeProxy<
        Tag extends keyof TypeProps,
        Map extends ArrayLike<any>,
        T,
        Params extends ArrayLike<any> = ArrayLike<any>
    > = {
        [Key in keyof util.ToTuple<Map>]: Key extends number
            ? Map[Key] extends Infer
                ? T extends never
                    ? never
                    : Key extends keyof TypeParams<T, Tag>
                        ? TypeParams<T, Tag>[Key]
                        : never
                : Map[Key] extends _<infer Index>
                    ? Params[Index]
                    : util.ToTuple<Map>[Key]
            : Map["length"]
    };

    type TypeParams<
        T,
        Tag extends keyof TypeProps = keyof TypeProps
    > = util.ToTuple<
        Infer extends T
            ? {
                  [index: number]: any;
                  length: TypeProps<T>[Tag]["infer"]["length"];
              }
            : TypeProps<T>[Tag]["infer"]
    >;

    type Generic<
        Tag extends keyof TypeProps,
        Params extends ArrayLike<any> = ArrayLike<any>,
        Map extends ArrayLike<any> = {
            [index: number]: _;
            length: Params["length"];
        },
        T = never
    > = TypeProps<TypeProxy<Tag, Map, T, Params>>[Tag]["construct"];

    namespace util {
        const InferSymbol: unique symbol;
        const Placeholder: {
            readonly 0: unique symbol;
            readonly 1: unique symbol;
            readonly 2: unique symbol;
            readonly 3: unique symbol;
            readonly 4: unique symbol;
            readonly 5: unique symbol;
            readonly 6: unique symbol;
            readonly 7: unique symbol;
            readonly 8: unique symbol;
            readonly 9: unique symbol;
        };

        type TupleKey = {
            [index: number]: number;
            1: 0;
            2: 0 | 1;
            3: 0 | 1 | 2;
            4: 0 | 1 | 2 | 3;
            5: 0 | 1 | 2 | 3 | 4;
            6: 0 | 1 | 2 | 3 | 4 | 5;
            7: 0 | 1 | 2 | 3 | 4 | 5 | 6;
            8: 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7;
            9: 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8;
        };

        type ToTuple<T extends ArrayLike<any>> = {
            [Key in TupleKey[T["length"]] | "length"]: T[Key]
        };
    }
}
masaeedu commented 6 years ago

That looks really nice @SimonMeskens, @nadameu! Could you give some info on "usage patterns" for this? E.g. what does the last parameter in Generic<Tag, [T], Map, never> represent?

I'm thinking of maybe trying out some of this stuff in a small library of instance implementations for native JS types that I have. If I can get accurate API docs for free that's worth whatever extra type noise I have to add I think.

SimonMeskens commented 6 years ago

So Generic<Tag, [T]> just means a HKT of type Tag (you can get tags with TypeTag<T>). The two other parameters come in when are writing code that is parametric over one type, but the type you give it potentially has multiple parameters.

I will eventually write some easier examples and documentation of course, but the gist of it is this:

Generic<"either", [number], [Infer, _], Either<string, string>> == Either<string, number>

so we take some either type, give it one parameter, then give it a map, which says "infer the first parameter, replace the second" and a type with parameters [string, string]. The resulting type is the same as Generic<"either", [string, number]>.

I'm hoping I can improve the API in such a way that this will become easier to work with, but this was needed to represent the Haskell concept of Monad (Either e). I have a solution planned that works more like Haskell, which is easier to work with, but that one is still in the works.

SimonMeskens commented 6 years ago

This one is now resolved too. Basically, I allow for combining type dictionaries, so you can have a MonadProps extends FunctorProps and combine that with the type dictionary. There's samples in the repo under lib/abstract/monad and examples/abstract/monad. This most closely mirrors Haskell's instance types, so it makes sense. It also heavily simplified my codebase.

SimonMeskens commented 6 years ago

Reopening this in light of @nadameu's new sample.

Playground

Pros/cons:

I think it provides an improvement over the current library, but not by much due its own set of complications towards developers of new abstract types (having to provide a unique key instead of a type dictionary). I'll probably make a branch for experimenting with this new design. Pulling it into master I want to postpone until after TS 2.9 comes out (the new keyof will complicate porting to the new version, I can migrate the branch immediately to the dev version and not have that issue).

The big difference between Haskell and TypeScript is that TS has a million different little variations of HKTs and Monads. They are mostly standardized thanks to fantasy-land, but there's still at least 4 big categories (fantasy-land, static-land, curried static-land and partially curried static-land). There are no multimethods, so the implementation side is interface-based.

What this all means is that you'd probably have a bunch of different monad interfaces out in the wild relying on TypeProps, as well as a number of different implementations of the same idea (it's very common to have dependencies on multiple promise and maybe libraries). All of these need their own unique id and this is one of the harder problems to tackle.