-
Notifications
You must be signed in to change notification settings - Fork 1.9k
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
Support higher kinded type #30
Comments
👍 |
Interesting. We have some ideas on how we can generalize type aliases to model higher kinds in the future; but I'm curious if you already have some concrete examples that you need this feature to type check. |
the mappable interface is pretty much the kind of interface that I'd like to use. interface List<A> extends Mappable<List<A>> {
....
// so we have map<B>(func: (a:A) => B): List<B>;
}
interface Sequence<A> extends Mappable<Sequence<A>> {
....
// so we have map<B>(func: (a:A) => B): Sequence<B>;
}
function map<M,A,B>(mappable: Mappable<M<A>>, func: (a:A) => B): M<B> {
return mappable.map(func)
}
function mapLenghth<M>(mappable: Mappable<M<{length : number}>>): M<number> {
return mappable.map(mappable, a => a.length);
}
var list: List<string>;
var lengthList = mapLength(list); // List<number>
var sequence: Sequence<string>;
var lengthSeq = mapLength(sequence); // Sequence<number>
var array: Array<string>;
var lengthArray = mapLength(array); // Array<number> since in fact
// Array<A> implements Mappable<Array<A>>
//etc... Note that in this example we always have mappable mapping to their own type, but we could have for example : class Sequence<A> implements Mappable<Array<A>> {
map<B>(func: (a:A) => B): Array<B> {
return Array.from(...this, func);
}
} |
It's necessary for defining monads (a special kind of mappable): interface Monad<A, M<>> {
static map<B>(f:(a:A) => B): M<A> => M<B>;
static lift(a:A): M<A>;
static join(mma: M<M<A>>): M<A>;
} Basically any collection type (even an asynchronous collection like a stream) implements the monad interface; so do parsers, promises, membranes, encapsulated state (particularly useful for serialized computations), and many more design patterns. Here's code for how one could implement the standard list monad. This particular monad is clunky because it's already built into javascript with special syntax, but the other features above turn out particularly nicely using this interface. class ArrayMonad<A> implements Monad<A, Array> {
static map<B>(f:(a:A) => B): Array<A> => Array<B> {
return function (arr: Array<A>): Array<B> {
return arr.map(f);
};
}
static lift(a:A): Array<A> { return [a]; }
static join(mma: Array<Array<A>>): Array<A> {
var result: Array<A> = [];
var len = mma.length;
for (var i = 0; i < len; ++i) {
result = result.concat(mma[i]);
}
}
}
ArrayMonad.map((x)=>''+x+x)([1,2,3,4]); // ['11','22','33','44']
ArrayMonad.lift(5); // [5]
ArrayMonad.join([[1,2,3],[4,5],[],[6]]); // [1,2,3,4,5,6] Another example: interface Cartesian<T, F<>> {
function all(arr: Array<F<T>>): F<Array<T>>;
}
class PromiseCartesian<T> implements Cartesian<T, Promise> {
function all(arr: Array<Promise<T>>): Promise<Array<T>> {
// return a promise that resolves once all the promises in arr have resolved
}
}
class MaybeCartesian<T> implements Cartesian<T, ?> {
// If all the elements of arr are non-null, returns arr; otherwise returns null.
function all(arr: Array<?T>): ?Array<T> {
var len = arr.length;
var result:Array<T> = [];
for (var i = 0; i < len; ++i) {
if (arr[i] == null) { return null; }
result.push(arr[i]);
}
return result;
}
} |
Here's another code dump that aligns more closely with the syntax we have now. type Monoid<T> = {
zero: T;
append: (a: T, b: T) => T;
}
// These are fine
var sumMonoid: Monoid<number> = {
zero: 0,
append: (a, b) => a + b
}
var productMonoid: Monoid<number> = {
zero: 1,
append: (a, b) => a * b
}
// The type variable T can't be free, but this
// formulation is valid for any type T.
var arrayMonoid: Monoid<Array<T>> = {
zero: [],
append: (a, b) => a.concat(b)
}
// We should be able to use any type, not just
// built-ins.
type Transformation<T> = (x: T) => T;
var transformationMonoid: Monoid<Transformation<T>> = {
zero: (x) => x,
append: (f, g) => (x) => f(g(x))
}
// HKT use case
function concat<T>(array: Array<T>, monoid: Monoid<T>): T {
return array.reduce(monoid.append, monoid.zero);
}
var ex1: number = concat([1, 2, 3], sumMonoid);
var ex2: number = concat([1, 2, 3], productMonoid);
var ex3: Array<number> = concat([[1, 2], [3, 4]], arrayMonoid);
var ex4: Transformation<number> = concat([(x) => x + 1, (x) => x - 1], transformationMonoid); Note that these "type classes" are a bit more cumbersome to use than they would be in Haskell, where the compiler will thread the dictionaries around for you, or Scala where the implicit rules will do the same. |
Oddball edge case. Given interface I {
method(): string;
static sMethod(): string;
} should the following error out? class A implements I {
method: void => string;
static sMethod: void => string;
constructor() {
this.method = function () { return 5; } // NG
}
}
A.sMethod = function () { return "peep"; } |
@popham I'm not sure how that is related to HKT. Can you elaborate? |
Off-topic. Apologies. (I'm imagineering what |
Looking for this feature as well. |
Higher order types should also include recursive types. This would help in cases like Lodash's
|
@popham Your example isn't really related to HKT either. Also, recursive types should work. I think your type definition is wrong. You probably want something like this: type Flattenable<A> = Array<A | Flattenable<A>>;
var foo: Flattenable<string> = ['a', ['a'], [['a']]]; // OK |
Also, for what it's worth. Using the existential types that are on master right now, my previous example with 2 character substitutions type checks: /* @flow */
type Monoid<T> = {
zero: T;
append: (a: T, b: T) => T;
}
// These are fine
var sumMonoid: Monoid<number> = {
zero: 0,
append: (a, b) => a + b
}
var productMonoid: Monoid<number> = {
zero: 1,
append: (a, b) => a * b
}
var arrayMonoid: Monoid<Array<*>> = {
zero: [],
append: (a, b) => a.concat(b)
}
type Transformation<T> = (x: T) => T;
var transformationMonoid: Monoid<Transformation<*>> = {
zero: (x) => x,
append: (f, g) => (x) => f(g(x))
}
// HKT use case (edit: not actually HKT)
function concat<T>(array: Array<T>, monoid: Monoid<T>): T {
return array.reduce(monoid.append, monoid.zero);
}
var ex1: number = concat([1, 2, 3], sumMonoid);
var ex2: number = concat([1, 2, 3], productMonoid);
var ex3: Array<number> = concat([[1, 2], [3, 4]], arrayMonoid);
var ex4: Transformation<number> = concat([(x) => x + 1, (x) => x - 1], transformationMonoid); |
Any update regarding this feature? There was no response within 30 days yet this feature request is not yet closed. I'd also like to see this become reality. |
Here's a (hacky) way to implement HKT: https://github.com/gcanti/flow-static-land |
Thanks, @gcanti -- that seems like a reasonable workaround for now. Just to follow up with something semi-official: There's a chance we may get to this at some point, but it's not on the near-term horizon for the core team. As we've done so far with this, we'll leave this issue open to track the request for if/when this surfaces the priority queue. |
Is there still no way to do this "officially"? |
@jeffmo In the year+ since your last comment, how has thinking on this by the core team changed if at all? |
From what I can tell, higher-kinded types are actually supported in Flow, though they are encoded using the simple-kinded function type and the utility type The encoding is as follows: Not sure if there is a way to encode kinds like |
@astampoulis this is @hallettj's encoding I'm currently using in fp-ts type Either<L, R> = { type: 'Left', value: L } | { type: 'Right', value: R }
type EitherF = <L, R>(x: [L, R]) => Either<L, R>
type ArrayF = <A>(x: [A]) => Array<A>
type ArrOfStrings = $Call<ArrayF, [string]>
type EitherOfStringNumber = $Call<EitherF, [string, number]>
;([1]: ArrOfStrings) // number. This type is incompatible with string
;({ type: 'Right', value: 'foo' }: EitherOfStringNumber) // string. This type is incompatible with number |
Any updates here? It's so hard to use Flow for functional programming without this. :( |
example :
The text was updated successfully, but these errors were encountered: