我有2个作曲功能:
compose
和composeR
。它们接收 2 个函数并将它们组合在一起,其中一个函数的输出作为输入传递给另一个函数。这是他们的定义:
type Compose = <A, B, C>(
f: (x: B) => C,
g: (x: A) => B
) => (x: A) => C
const compose: Compose =
(f, g) => x => f(g(x))
type ComposeR = <A, B, C>(
g: (x: A) => B
f: (x: B) => C,
) => (x: A) => C
const composeR: ComposeR =
(g, f) => x => g(f(x))
现在尝试编写如下所示的函数,所有内容都可以编译并且所有类型都可以正确推断:
type Increment = (x: number) => number
const increment: Increment = x => x+1
type ToString = (x: number) => string
const toString: ToString => x => `${x}`
const composed = compose(toString, increment)
const composedR = compose(increment, toString)
composed(12) // "13"
composedR(12) // "13"
但是如果我尝试更复杂的输入函数,我就会遇到
compose()
函数的编译问题:
type Option<A> = Some<A> | None
interface Some<A> {
_tag: 'Some'
value: A
}
interface None {
_tag: 'None'
}
const some = <A,>(x: A): Option<A> =>
({ _tag: 'Some', value: x })
const none: Option<never> =
{ _tag: 'None' }
const isNone = <A,>(x: Option<A>): x is None =>
x._tag === 'None'
// --------------------
type Either<E, A> = Left<E> | Right<A>
interface Left<E> {
_tag: 'Left'
left: E
}
interface Right<A> {
_tag: 'Right'
right: A
}
const left = <E,A=never>(x: E): Either<E, A> => ({ _tag: 'Left', left: x})
const right = <A,E=never>(x: A): Either<E, A> => ({ _tag: 'Right', right: x})
const isLeft = <E, A>(a: Either<E, A>): a is Left<E> =>
a._tag === 'Left'
// --------------------
interface URItoKind1<A> {
'Option': Option<A>
}
interface URItoKind2<E,A> {
'Either': Either<E,A>
}
type URIS1 = keyof URItoKind1<any>
type URIS2 = keyof URItoKind2<any, any>
type Kind1<URI extends URIS1, A> = URItoKind1<A>[URI]
type Kind2<URI extends URIS2, E, A> = URItoKind2<E,A>[URI]
type HKT1<URI, A> = { URI: URI; a: A };
type HKT2<URI, A, B> = { URI: URI; a: A; b: B }
interface Functor1<F extends URIS1> {
readonly URI: F
map: <A, B>(f: (a: A) => B) => (fa: Kind1<F, A>) => Kind1<F, B>
}
interface Functor2<F extends URIS2> {
readonly URI: F
map: <E, A, B>(f: (a: A) => B) => (fa: Kind2<F, E, A>) => Kind2<F, E, B>
}
interface Functor<F> {
readonly URI: F
map: <A, B>(f: (a: A) => B) => (fa: HKT1<F, A>) => HKT1<F, B>
}
// --------------------------
const optionFunctor: Functor1<'Option'> = {
URI: 'Option',
map: <A,B>(f: (x: A) => B) => (fa: Option<A>): Option<B> =>
isNone(fa) ? none : some(f(fa.value))
}
const eitherFunctor: Functor2<'Either'> = {
URI: 'Either',
map: <E,A,B>(f: (x: A) => B) => (fa: Either<E, A>): Either<E, B> =>
isLeft(fa) ? fa : right(f(fa.right))
}
// ---------------------------
type Compose = <A, B, C>(
f: (x: B) => C,
g: (x: A) => B
) => (x: A) => C
const compose: Compose =
(f, g) => x => f(g(x))
type ComposeR = <A, B, C>(
g: (x: A) => B,
f: (x: B) => C
) => (x: A) => C
const composeR: ComposeR =
(g, f) => x => f(g(x))
// ---------------------------
type Increment = (x: number) => number
const increment: Increment = x => x+1
type ToStringg = (x: number) => string
const toStringg: ToStringg = x => `${x}`
const composed = compose(toStringg, increment)
const composedR = composeR(increment, toStringg)
composed(12) // "13"
composedR(12) // "13"
// This section compiles ok and types inferred correctly when composing functions.
// ---------------------------
const map1 = optionFunctor.map
const map2 = eitherFunctor.map
const composed1 = compose(map1, map2) // <=== map2 has error and types cannot be inferred correctly
const composed2 = composeR(map1, map2) // <=== map2 is ok here!
// Try switching map1 and map2. Why in `composed1` TypeScript cannot infer types correctly? how can I fix it?
发生的情况是,
compose
无法正确推断类型。但如果我尝试使用 composeR()
类型,则推断没有问题。为什么 compose()
函数无法正确推断类型?我该如何解决这个问题?
这被认为是 TypeScript 的设计限制。请参阅 microsoft/TypeScript#31738。
TypeScript 对您试图在此处实现的高阶泛型函数传播的支持非常有限。此支持在 TypeScript 3.4 中发布并在 microsoft/TypeScript#30215 中实现,仅适用于非常特定的情况,并且您的
Compose
类型不受支持。
根据microsoft/TypeScript#30215的描述,高阶函数推理
不是一个完整的统一算法,也绝不是完美的。特别是,它仅在类型从左向右流动时才提供所需的结果。然而,TypeScript 中的类型参数推断一直都是这种情况,并且它具有在代码键入时运行良好的高度期望的属性。
在您的
Compose*
类型中,您希望 TypeScript 通过使用 A
输入(从 C
到 g
的泛型类型参数)来合成泛型函数(从
A
到
B
的泛型函数))并传播到
f
输入(从 B
到 C
的输入)。
您的
ComposeR
类型是
type ComposeR = <A, B, C>(g: (x: A) => B, f: (x: B) => C) => (x: A) => C
您可以看到从
g
到 f
所需的流程与参数的顺序相同。左到右。所以它有效。但在Compose
,
type Compose = <A, B, C>(f: (x: B) => C, g: (x: A) => B) => (x: A) => C
从
g
到f
所需的流程必须以相反的顺序发生。右到左。所以它不起作用。
所以这就是发生这种情况的原因。它不能轻易修复。除非向 TypeScript 添加一些功能更齐全的通用支持(可能涉及microsoft/TypeScript#1213中讨论的更高级的类型),否则您将不得不放弃或解决它。