FunctionK与自然变换为何不同?求非等价实例及相关疑问
一、两者不等价的实例
1. 不属于自然变换的FunctionK
自然变换的核心要求是满足交换图:对于任意函数f: A => B,必须有G(f) ∘ φ = φ ∘ F(f)(φ是变换,F、G是函子)。而FunctionK只是Scala中参数多态的类型构造器映射,只要是F[_] => G[_]的参数多态函数都可以是FunctionK,不强制满足自然性。
比如定义一个依赖具体类型的FunctionK:
import cats.FunctionK import scala.reflect.ClassTag val nonNaturalFK: FunctionK[List, Option] = new FunctionK[List, Option] { def apply[A](fa: List[A])(implicit ct: ClassTag[A]): Option[A] = if (ct == ClassTag.String) fa.headOption else None }
这个FunctionK不满足自然性:取函数f: Int => String,当输入List(1)时:
- 左边
Option(f) ∘ nonNaturalFK:nonNaturalFK(List(1))返回None,None.map(f)还是None; - 右边
nonNaturalFK ∘ List.map(f):List(1).map(f)得到List("1"),nonNaturalFK(List("1"))返回Some("1");
两边结果完全不同,因此它不是自然变换。
2. 无法用FunctionK表示的自然变换
范畴论中的自然变换可以在反变函子之间存在,但Scala的FunctionK仅支持协变类型构造器(F[_]),无法直接表示反变函子间的自然变换。
比如反变函子F[A] = A => Boolean(谓词)和G[A] = A => Int,它们之间的自然变换可以是:
def nat[A](f: A => Boolean): A => Int = a => if (f(a)) 1 else 0
这个变换满足反变函子的自然性条件(对于函数g: B => A,G(g) ∘ nat = nat ∘ F(g)),但由于F和G是反变类型构造器(F[-_]),无法用FunctionK[F, G]来表示。
二、关于Functor实例的要求
范畴论中,自然变换的定义前提是F和G必须是函子(协变或反变均可)。对应到Cats的语境:
- 若要让
FunctionK[F, G]成为自然变换,F和G必须拥有对应的函子实例:协变函子对应Functor,反变函子对应Contravariant。因为自然变换的交换图依赖于函子的map(协变)或contramap(反变)操作,没有这些实例就无法验证自然性。 - 维基百科提到的反变函子替换协变函子,只是自然变换的适用范围扩展,反变函子本身依然是函子的一种,并非不需要函子结构。
三、关于不纯FunctionK的疑问
确实,包含副作用、依赖外部状态的不纯FunctionK会打破范畴论的自然变换性质,因为范畴论中的态射默认是纯函数。不过Cats的FunctionK本身不强制纯性,这类不纯实例自然不属于自然变换。文档特意提及,可能是为了补充说明:即使是纯的FunctionK,也可能因为不满足自然性条件而不是自然变换(比如前面依赖具体类型的纯FunctionK)。
内容的提问来源于stack exchange,提问作者Joe

