Reputation: 2287
I have a library of helper functions that I want to export curried versions of.
A small chunk of it looks like this:
export function curry2<A,B,C>(f: (x: A, y: B) => C): (x: A) => (y: B) => C {
return (a) => (b) => f(a, b)
}
function _push<A>(item: A, items: Array<A>): Array<A> {
return items.concat(item)
}
export const push = curry2(push)
But that doesn't work. Flow complains expression curry2(push)
, saying:
- type parameter 'A' of function call. Missing annotation.
- type parameter 'B' of function call. Missing annotation.
So I tried to fix this by annotating the exported identifier:
export const push<A>: (item: A) => (items: Array<A>) => Array<A>
But this doesn't work because const
expressions can't introduce generic type variables.
So I figured I would have to export an actual function in order to be able to annotate it:
export function push<A> (item: A): (items: Array<A>) => Array<A> {
return curry2(_push)(item);
}
But at this point I'm basically going to re-write a big chunk of curry for each function I want to export.
Is there a better way to help Flow fill in the generic type variables of exports in const expressions?
Upvotes: 0
Views: 623
Reputation: 6481
Type inference with subtyping, polymorphism, and compact types has traditionally been problematic. There's a recent paper describing a modification to Hindley-Milner inference which might be useful if it could be adapted to Flow.
Anyway, Flow does actually seem to allow some polymorphic const values. For example
// @flow strict
const id: <T>(T) => T = <T>(a: T):T => a;
const genericConstant: <T>(T) => T = id(id);
const dup: <U>(U) => [U, U] = <U>(a: U) => [a, a];
const iddup: <T>(T) => [T, T] = id(dup);
const dupdup: [<T>(T) => [T, T], <U>(U) => [U, U]] = dup(dup);
const curry2 = <A,B,C>(f: (A, B) => C) => (a: A) => (b: B): C => f(a, b);
function _push<A>(item: A, items: Array<A>): Array<A> {
return items.concat(item)
}
const push2 /*broken : <A>(A) => Array<A> => Array<A> */ = curry2(_push)
const pair: <A,B>(A, B) => [A, B] = <A, B>(a: A, b: B): [A, B] => [a, b]
const pair2 /*broken: <A,B>(A) => (B) => [A, B]*/ = curry2(pair)
I'm not sure what rule it is following, but it's definitely different from SML's value restriction.
Upvotes: 0
Reputation: 235
See my answer to the same question here: https://github.com/facebook/flow/issues/2165#issuecomment-236868389
The main limitation here is that Flow doesn't infer polymorphic types at all. In particular, whenever it sees a call to a polymorphic function, it immediately instantiates the type parameters with fresh type arguments, and the result is never "generalized" back the way Hindley-Milner systems (https://en.wikipedia.org/wiki/Hindley%E2%80%93Milner_type_system) do.
The reason for this limitation is that this kind of polymorphic type inference is undecidable with subtyping (see Pierce, "Bounded quantification is undecidable", POPL'92), and subtyping is a necessary feature for JavaScript (but not so much for a ML-like language).
Upvotes: 2
Reputation: 1462
I suspect is not possible to define a curry2
function without higher kinded types
type Function2<A, B, C> = (a: A, b: B) => C;
type CurriedFunction2<A, B, C> = (a: A) => (b: B) => C;
export function curry2<A, B, C>(f: Function2<A, B, C>): CurriedFunction2<A, B, C> {
return (a: A) => (b: B): C => f(a, b)
}
export const push = curry2(function <A>(item: A, items: Array<A>): Array<A> {
return items.concat(item)
})
The last definition means that push
has type
push: CurriedFunction2<A, Array<A>, Array<A>> // <= higher kinded type?
Upvotes: 0