You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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的类型兼容性规则和运行时的对象扩展逻辑:

  1. 类型层面的合法性

    • g的入参要求是TRSignature类型:一个接收两个字符串参数、返回包含o1/o2的对象的函数。
    • f(r)返回的函数,入参类型和TRSignature完全一致,返回值则是原返回值的超集(多了o3属性)。
    • TypeScript对函数类型的兼容性遵循参数逆变、返回协变规则:
      • 参数类型完全匹配,满足逆变要求;
      • 返回值的超集类型可以安全赋值给子集类型(因为使用该函数时只会访问o1/o2,额外的o3不会引发类型错误)。
    • 因此f(r)返回的函数完全符合g的入参类型要求,不会触发类型检查错误。
  2. 运行时的逻辑合理性

    • g内部调用传入的rIn函数时,使用对象展开运算符...会保留rIn返回的所有属性(包括f添加的o3),再额外添加o4,最终自然会输出所有属性,运行时没有任何问题。

二、附加问题:f和g是函子吗?

不是,原因如下:
函子的核心是一个包含值的容器,实现了通用的map方法,且满足恒等律、结合律两个定律:

  • 恒等律:对容器应用恒等函数,结果与原容器一致;
  • 结合律:map(f ∘ g) 与 map(f) ∘ map(g) 的结果等价。

而f和g只是特定的高阶函数:

  1. 它们不是通用的映射操作——硬编码添加了固定属性(o3/o4),无法接收任意变换函数来处理目标函数的输出;
  2. 不满足函子定律:比如恒等律,f会给原函数的输出强行添加o3,不可能返回原函数;结合律也不成立,因为它们的逻辑是固定扩展,无法兼容函数组合的映射规则。

内容的提问来源于stack exchange,提问作者mzedeler

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.16 01:55:19