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

Haskell中(fmap.fmap) func与fmap(fmap func)返回类型差异疑问

问题核心原因:函数优先级与泛型参数的实例选择差异

你看到的类型签名一致只是泛型层面的一致,实际因为两个写法的参数绑定逻辑不同,编译器为泛型的Functor选择了完全不同的实例,最终导致返回类型不同。

先理清两个写法的实际执行逻辑

Haskell中函数应用的优先级高于所有运算符,我们把两个定义展开就一目了然:

  1. 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的错误结构。
  2. 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 IOException
  • fileChars'里传入的是FilePath -> IO (...)类型的函数,所以f1=(->) FilePath、f2=IO
    实例选择不同,最终的返回类型自然就不一样了。

验证小测试

如果你把fileChars'的定义改成加函数组合的版本:

fileChars' = (fmap.fmap) length . safeLoad

它的返回类型就会和fileChars完全一致,都是IO (Either IOException Int)。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.02 01:30:05