Reader函子的fmap实例如何满足函子定义?类型签名解析
Reader函子fmap实例的疑问解答
问题背景
正在阅读Bartosz Milewski所著的《Category Theory for Programmers》第164页时,遇到Reader函子的定义及fmap实例:
newtype Reader e a = Reader (e -> a) instance Functor (Reader e) where fmap f (Reader g) = Reader (\x -> f (g x))
自行猜测的fmap类型签名为:
fmap :: (a -> b) -> (Reader e -> a) -> (Reader e -> b)
存在以下困惑:
- 函子声明中使用了部分应用的
Reader e f、g两个函数参数的作用,以及lambda表达式中x的含义(是否为外部值)- 类型参数
e在fmap实例中的作用
核心疑问:该fmap实例如何满足函子定义的fmap :: (a->b) -> f a -> f b?
解答
1. 纠正你的fmap类型签名错误
你猜测的类型签名是错的,针对Reader e这个函子,正确的fmap类型应该是:
fmap :: (a -> b) -> Reader e a -> Reader e b
这才是对函子定义fmap :: (a->b) -> f a -> f b的实例化——这里的f就是部分应用后的Reader e,而非Reader本身。
2. 为什么要用部分应用的Reader e?
Haskell的Functor类型类要求实例必须是接受单个类型参数的构造器。Reader本身是接受两个类型参数的构造器(Reader e a),当我们固定第一个参数e后,Reader e就变成了只接受一个类型参数a的构造器,刚好符合Functor的要求。
3. 拆解fmap里的各个元素
f:就是函子定义里的a->b函数,负责把a类型的值转换成b类型。g:是被Reader包装的函数,类型是e -> a——因为Reader e a的本质就是包装了一个从环境e到值a的函数。\x -> f (g x):这个lambda是一个新的函数,类型是e -> b。这里的x不是外部值,它是这个新函数的输入参数,类型就是e。当你最终调用这个新的Reader里的函数时,传入的x就是具体的环境值,g x会生成a类型的值,再经过f转换得到b类型的值。
4. e的作用是什么?
e是环境类型,Reader函子的核心是“依赖环境的计算”——它包装的函数需要一个环境值才能产出结果。在fmap中,e的作用是被透明传递的:我们不需要修改环境本身,只是把“从环境生成a”的计算,转换成“从环境生成b”的计算,整个过程中环境e的传递完全不被干扰。
5. 如何匹配函子的fmap定义?
把函子定义里的f替换成Reader e:
- 函子定义中的
f a→ 对应Reader e a(包装了e->a的函数) - 函子定义中的
f b→ 对应Reader e b(包装了e->b的函数) - fmap的逻辑就是:接收一个
a->b的函数,以及一个包装了e->a的Reader e a,返回一个包装了e->b的Reader e b——完全符合fmap :: (a->b) -> f a -> f b的定义。
内容的提问来源于stack exchange,提问作者qwertyman9938
相关产品推荐
相关产品推荐

