论运算符优先级的重要性
· 阅读需 6 分钟
没定义运算符优先级而引发的一系列锅
日常看书,看到Applicative typeclass,遂动手实现一下:
{-# LANGUAGE NoImplicitPrelude #-}
import Prelude hiding (Applicative(..))
class Functor f => Applicative f where
pure :: a -> f a
(<*>) :: f (a -> b) -> f a -> f b
instance Applicative Maybe where
pure = Just
Just f <*> m = fmap f m
Nothing <*> _ = Nothing
main :: IO ()
main = do
putStr $ (shows $ Just (+) <*> Just 1 <*> Just 2) "\n"
putStr $ (shows $ (+) `fmap` Just 1 <*> Just 2) "\n"
-- 以下这句会报错
putStr $ (shows $ (+) <$> Just 1 <*> Just 2) "\n"
之前一直都这么写,都没问题,然后这里最后一句会报错,编译不通过
09-Typeclass.hs:17:13: error:
? No instance for (Show (a0 -> a0)) arising from a use of ‘shows’
(maybe you haven't applied a function to enough arguments?)
? In the expression: shows $ (+) <$> Just 1 <*> Just 2
In the second argument of ‘($)’, namely
‘(shows $ (+) <$> Just 1 <*> Just 2) "\n"’
In a stmt of a 'do' block:
putStr $ (shows $ (+) <$> Just 1 <*> Just 2) "\n"
09-Typeclass.hs:17:21: error:
? Ambiguous type variable ‘a0’ arising from a use of ‘+’
prevents the constraint ‘(Num a0)’ from being solved.
Probable fix: use a type annotation to specify what ‘a0’ should be.
These potential instances exist:
instance Num Integer -- Defined in ‘GHC.Num’
instance Num Double -- Defined in ‘GHC.Float’
instance Num Float -- Defined in ‘GHC.Float’
...plus two others
...plus three instances involving out-of-scope types
(use -fprint-potential-instances to see them all)
? In the first argument of ‘(<$>)’, namely ‘(+)’
In the second argument of ‘($)’, namely ‘(+) <$> Just 1 <*> Just 2’
In the expression: shows $ (+) <$> Just 1 <*> Just 2
09-Typeclass.hs:17:37: error:
? No instance for (Num (a1 -> a0)) arising from the literal ‘1’
(maybe you haven't applied a function to enough arguments?)
? In the first argument of ‘Just’, namely ‘1’
In the first argument of ‘(<*>)’, namely ‘Just 1’
In the second argument of ‘(<$>)’, namely ‘Just 1 <*> Just 2’
09-Typeclass.hs:17:48: error:
? Ambiguous type variable ‘a1’ arising from the literal ‘2’
prevents the constraint ‘(Num a1)’ from being solved.
Probable fix: use a type annotation to specify what ‘a1’ should be.
These potential instances exist:
instance Num Integer -- Defined in ‘GHC.Num??
instance Num Double -- Defined in ‘GHC.Float’
instance Num Float -- Defined in ‘GHC.Float’
...plus two others
...plus three instances involving out-of-scope types
(use -fprint-potential-instances to see them all)
? In the first argument of ‘Just’, namely ‘2’
In the second argument of ‘(<*>)’, namely ‘Just 2’
In the second argument of ‘(<$>)’, namely ‘Just 1 <*> Just 2’
但单独拿出来,不重定义ap,则可以正常运行
main :: IO ()
main = do
putStr $ (shows $ Just (+) <*> Just 1 <*> Just 2) "\n"
putStr $ (shows $ (+) `fmap` Just 1 <*> Just 2) "\n"
putStr $ (shows $ (+) <$> Just 1 <*> Just 2) "\n"
输出结果
ghci> main
Just 3
Just 3
Just 3
ghci给出的报错信息不明确,稍作尝试:
先注释掉最后一句
ghci> :t (+) `fmap` Just 1 <*> Just 2
(+) `fmap` Just 1 <*> Just 2 :: Num b => Maybe b
ghci> :t (+) <$> Just 1 <*> Just 2
(+) <$> Just 1 <*> Just 2
:: (Num (a -> a1), Num a, Num a1) => Maybe (a1 -> a1)
发现返回了一个函数,上面两句的差别只有fmap
与<$>
但后者就是前者的别名
继续做尝试:
ghci> :t (+) `fmap` Just 1
(+) `fmap` Just 1 :: Num a => Maybe (a -> a)
ghci> :t (+) <$> Just 1
(+) <$> Just 1 :: Num a => Maybe (a -> a)
看上去也没问题,但是继续传值,结果就不一样了
看类型签名也没问题
ghci> :t fmap
fmap :: Functor f => (a -> b) -> f a -> f b
ghci> :t (<$>)
(<$>) :: Functor f => (a -> b) -> f a -> f b
ghci> :t (<*>)
(<*>) :: Applicative f => f (a -> b) -> f a -> f b
又怀疑是<*>
的定义有问题,翻了翻文档(Control.Applicative)
该有的定义也不少,而且Just (+) <*> Just 1 <*> Just 2
也能正常计算
尝试定义liftA2也是报错
liftA2 :: Applicative f => (a -> b -> c) -> f a -> f b -> f c
liftA2 f a b = f <$> a <*> b
报错
09-Typeclass.hs:19:16: error:
? Couldn't match type ‘c’ with ‘b -> c’
‘c’ is a rigid type variable bound by
the type signature for:
liftA2 :: forall (f :: * -> *) a b c.
Applicative f =>
(a -> b -> c) -> f a -> f b -> f c
at 09-Typeclass.hs:18:11
Expected type: f c
Actual type: f (b -> c)
? In the expression: f <$> a <*> b
In an equation for ‘liftA2’: liftA2 f a b = f <$> a <*> b
? Relevant bindings include
b :: f b (bound at 09-Typeclass.hs:19:12)
f :: a -> b -> c (bound at 09-Typeclass.hs:19:8)
liftA2 :: (a -> b -> c) -> f a -> f b -> f c
(bound at 09-Typeclass.hs:19:1)
09-Typeclass.hs:19:22: error:
? Couldn't match type ‘a’ with ‘b -> a’
‘a’ is a rigid type variable bound by
the type signature for:
liftA2 :: forall (f :: * -> *) a b c.
Applicative f =>
(a -> b -> c) -> f a -> f b -> f c
at 09-Typeclass.hs:18:11
Expected type: f (b -> a)
Actual type: f a
? In the first argument of ‘(<*>)’, namely ‘a’
In the second argument of ‘(<$>)’, namely ‘a <*> b’
In the expression: f <$> a <*> b
? Relevant bindings include
b :: f b (bound at 09-Typeclass.hs:19:12)
a :: f a (bound at 09-Typeclass.hs:19:10)
f :: a -> b -> c (bound at 09-Typeclass.hs:19:8)
liftA2 :: (a -> b -> c) -> f a -> f b -> f c
(bound at 09-Typeclass.hs:19:1)
依旧是参数类型不匹配
中间看官方库实现时,看到(GHC-Base.html#Applicative)里开头的 NOTA BENE
NOTA BENE: Do NOT use ($) anywhere in this module! The type of ($) is
slightly magical (it can return unlifted types), and it is wired in.
But, it is also *defined* in this module, with a non-magical type.
GHC gets terribly confused (and *hangs*) if you try to use ($) in this
module, because it has different types in different scenarios.
This is not a problem in general, because the type ($), being wired in, is not
written out to the interface file, so importing files don't get confused.
The problem is only if ($) is used here. So don't!
果然haskell到处都是魔法,但最后一段又说导出的库不受影响。
我把$
换为括号也没什么效果。
然后我想到群里提问,想了一下,还是先小黄鸭debug一下,于是假装已经提问了,出去走了几圈,半路上想ap的定义和库里的一样,应该没什么问题,估计锅还在<$>
上,<$>
的定义就是(<$>) = fmap
,理论上应该没问题,fmap
是一个函数要加反引号才能当做中缀运算符,进一步联想到到运算符有优先级,函数默认优先级最高。想到这一点就有继续做尝试。
然后果然是这样。加上 infixl 4 <*>
就好使了