fmap与<$>的编译差异问题:为何仅前者可正常运行?
fmap能正常运行但<$>却无法编译? 这是个很常见的坑,我之前也踩过!大概率是下面这几个原因之一,咱们一个个来看:
1. 你可能忘记导入Control.Applicative模块
虽然fmap属于Functor类型类,而Haskell默认导入的Prelude模块已经包含了Functor的内容,所以fmap可以直接用。但<$>本质上是Applicative类型类里的运算符(它确实是fmap的中缀别名,但在旧版Haskell里归属Control.Applicative模块),如果你的环境没有默认导入这个模块,编译器就找不到<$>的定义,自然会报错。
举个例子,这段代码会编译失败:
main = print $ (+1) <$> Just 5
只需要在开头加上导入语句就能解决:
import Control.Applicative -- 导入后就能正常使用<$>了 main = print $ (+1) <$> Just 5
(注:在较新的GHC版本中,Prelude已经默认包含了Applicative的部分内容,所以可能不需要手动导入,但如果是旧版本或者自定义了Prelude,还是得加这行。)
2. 中缀运算符的优先级/结合性导致解析错误
<$>是中缀运算符,它的优先级比普通函数调用要低。如果你的代码里没有给复杂的参数加括号,编译器会按照它的规则解析,结果可能和你预期的完全不一样,进而导致类型不匹配的错误。而fmap作为前缀函数,参数是明确用括号分隔的,所以不会有这个问题。
比如你写了这样的代码:
main = print $ (+1) <$> Just 5 + 3
编译器会把它解析成:((+1) <$> Just 5) + 3——左边是Maybe Int类型,右边是Int,这俩根本没法相加,自然编译报错。
而对应的fmap写法你可能会写成:
main = print $ fmap (+1) (Just (5 + 3))
这里你明确给5+3加了括号,编译器能正确识别Just的参数,所以没问题。要修复<$>的版本,只需要给5+3加上括号就行:
main = print $ (+1) <$> Just (5 + 3)
3. 不小心覆盖了<$>的定义
如果你自己在代码里定义了一个名为<$>的运算符,那编译器会优先使用你定义的版本,而不是标准库的fmap别名。这时候如果你的自定义运算符类型和预期不符,就会导致编译错误,而fmap因为没被覆盖,所以能正常工作。
检查一下你的代码里有没有类似这样的定义:
(<$>) :: Int -> Int -> Int a <$> b = a * b
如果有的话,要么改掉这个自定义运算符的名字,要么用限定导入来区分标准库的版本:
import qualified Control.Applicative as A main = print $ (+1) A.<$> Just 5
内容的提问来源于stack exchange,提问作者plx

