为什么我的其中一个 compose 函数无法推断类型?

问题描述 投票:0回答:1

我有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 function-composition
1个回答
0
投票

这被认为是 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中讨论的更高级的类型),否则您将不得不放弃或解决它。

© www.soinside.com 2019 - 2024. All rights reserved.