TypeScript高阶函数组合为何未报错?兼问f、g是否为函子
为什么这段TypeScript高阶函数组合代码能正常运行?
问题描述
我有一个反向问题——为什么以下代码能够正常运行?
我们使用一阶基础函数r,以及两个高阶函数f和g,这两个高阶函数会修改与r签名相同的函数的输出结构。为何如下所示的组合操作不会触发错误?毕竟f(r)返回的新函数输出结构已经发生了变化。
type TRSignature = (i1: string, i2: string) => { o1: string, o2: string } const r: TRSignature = (i1, i2) => ({ o1: `foo: ${i1}`, o2: `bar: ${i2}` }) type TFSignature = (rIn: TRSignature) => (...args: Parameters<TRSignature>) => ReturnType<TRSignature> & { o3: string } const f: TFSignature = rIn => (i1, i2) => ({...rIn(i1, i2), o3: '(through f)' }) type TGSignature = (rIn: TRSignature) => (...args: Parameters<TRSignature>) => ReturnType<TRSignature> & { o4: string } const g: TGSignature = rIn => (i1, i2) => ({...rIn(i1, i2), o4: '(through g)' }) console.log(g(f(r))('hello', 'there'))
输出:
{ o1: 'foo: hello', o2: 'bar: there', o3: '(through f)', o4: '(through g)' }
附加问题:f和g是否属于函子(functor)?
回答
一、代码正常运行的原因
核心在于TypeScript的类型兼容性规则和运行时的对象扩展逻辑:
类型层面的合法性
g的入参要求是TRSignature类型:一个接收两个字符串参数、返回包含o1/o2的对象的函数。f(r)返回的函数,入参类型和TRSignature完全一致,返回值则是原返回值的超集(多了o3属性)。- TypeScript对函数类型的兼容性遵循参数逆变、返回协变规则:
- 参数类型完全匹配,满足逆变要求;
- 返回值的超集类型可以安全赋值给子集类型(因为使用该函数时只会访问
o1/o2,额外的o3不会引发类型错误)。
- 因此
f(r)返回的函数完全符合g的入参类型要求,不会触发类型检查错误。
运行时的逻辑合理性
g内部调用传入的rIn函数时,使用对象展开运算符...会保留rIn返回的所有属性(包括f添加的o3),再额外添加o4,最终自然会输出所有属性,运行时没有任何问题。
二、附加问题:f和g是函子吗?
不是,原因如下:
函子的核心是一个包含值的容器,实现了通用的map方法,且满足恒等律、结合律两个定律:
- 恒等律:对容器应用恒等函数,结果与原容器一致;
- 结合律:
map(f ∘ g)与map(f) ∘ map(g)的结果等价。
而f和g只是特定的高阶函数:
- 它们不是通用的映射操作——硬编码添加了固定属性(
o3/o4),无法接收任意变换函数来处理目标函数的输出; - 不满足函子定律:比如恒等律,
f会给原函数的输出强行添加o3,不可能返回原函数;结合律也不成立,因为它们的逻辑是固定扩展,无法兼容函数组合的映射规则。
内容的提问来源于stack exchange,提问作者mzedeler
相关产品推荐
相关产品推荐

