Haskell中(fmap.fmap) func与fmap(fmap func)返回类型差异疑问
问题核心原因:函数优先级与泛型参数的实例选择差异
你看到的类型签名一致只是泛型层面的一致,实际因为两个写法的参数绑定逻辑不同,编译器为泛型的Functor选择了完全不同的实例,最终导致返回类型不同。
先理清两个写法的实际执行逻辑
Haskell中函数应用的优先级高于所有运算符,我们把两个定义展开就一目了然:
fileChars的展开原定义:
fileChars = fmap (fmap length) . safeLoad等价于:
fileChars fp = fmap (fmap length) (safeLoad fp)这里
fmap (fmap length)作用的目标是safeLoad fp的返回值,也就是IO (Either IOException String)类型的值:- 第一层
fmap对应IO的Functor实例,把内层的fmap length作用到Either IOException String上 - 第二层
fmap对应Either IOException的Functor实例,把length作用到最内层的String上 - 最终得到的就是
IO (Either IOException Int),保留了Either的错误结构。
- 第一层
fileChars'的展开原定义:
fileChars' = (fmap.fmap) length safeLoad等价于:
fileChars' = (fmap (fmap length)) safeLoad -- 进一步因为函数类型(-> FilePath)本身是Functor,fmap对函数来说就是组合,所以等价于: fileChars' fp = fmap length (safeLoad fp)这里
(fmap.fmap) length作用的目标是safeLoad这个函数本身,所以两层Functor的实例完全变了:- 第一层
fmap对应(-> FilePath)(函数类型)的Functor实例,本质就是函数组合 - 第二层
fmap对应IO的Functor实例,把length直接作用到IO包裹的Either IOException String上 - 而
Either e本身是Foldable类型类的实例,length对Either求值时,Left _返回0,Right _返回1,直接抹掉了Either结构得到Int - 最终返回的就是
IO Int。
- 第一层
为什么ghci里看两个函数的类型签名完全一致?
因为ghci输出的是泛型签名:
ghci> :t (fmap (fmap length)) (fmap (fmap length)) :: (Functor f1, Functor f2, Foldable t) => f1 (f2 (t a)) -> f1 (f2 Int)
这里的f1、f2都是泛型参数,具体选哪个实例完全取决于你传入的参数:
fileChars里传入的是IO (Either ...)类型的值,所以f1=IO、f2=Either IOExceptionfileChars'里传入的是FilePath -> IO (...)类型的函数,所以f1=(->) FilePath、f2=IO
实例选择不同,最终的返回类型自然就不一样了。
验证小测试
如果你把fileChars'的定义改成加函数组合的版本:
fileChars' = (fmap.fmap) length . safeLoad
它的返回类型就会和fileChars完全一致,都是IO (Either IOException Int)。
内容的提问来源于stack exchange,提问作者vijaicv
相关产品推荐
相关产品推荐

