举个例子,请参考以下内容
type Row a = [a]
type Table a = [Row a]
mapTable :: (a -> b) -> Table a -> Table b
mapTable = map . map
notTable :: Table Bool -> Table Bool
notTable = map . map $ (not)
为什么,如果我从notTable中删除$,它会停止工作吗?
我已经对自己解释了几次,但它永远不会坚持下去,我需要一段时间才能推断出最新情况。我知道$基本上确保$的每一边都被单独评估,因为$具有最低的优先级,但是如果我把$拉出来,为什么会中断呢?
由于
答案 0 :(得分:10)
你的优先权是正确的:.
是infixr 9(9是最高的),而$
是infixr 0(0是最低的)。有关操作员固定性表,请参阅Haskell Report。
但是,函数应用程序的优先级高于任何运算符,甚至.
。因此:
map . map $ (not)
变为:
(map . map) $ (not)
,而
map . map (not)
变为:
map . (map not)
答案 1 :(得分:3)
回答你问的问题,作为对Joey Adams回答的评论:“为什么map . map $ not = (map . map) $ not
有效,而map . map not = map . (map not)
没有?”
让我们首先考虑map . map
的作用。首先,map
采用函数f :: a -> b
和类型为[a]
的列表,提供类型为[b]
的列表,其中f
应用于每个元素原始清单。 map
的类型为(a -> b) -> [a] -> [b]
。回想一下,在Haskell中,这意味着map
实际上是一个函数,它接受函数a -> b
并返回一个函数[a]
并给出[b]
。我们经常喜欢将其视为map
是两个变量的函数,但这种区别在以后会很重要。
现在让我们考虑一下合成运算符(.)
的作用。回想一下它被定义为
(.) :: (b1 -> c1) -> (a1 -> b1) -> (a1 -> c1)
f . g = \ x -> f (g x)
,即它需要两个函数f
和g
(具有合适的域/输入和目标/输出),并为您提供一个新函数,该函数首先应用g
然后应用f
g
吐出的a1
。我调用了类型变量b1
,c1
和map . map
以避免以后混淆。
好的,现在我们可以找出mapleft :: (c -> d) -> [c] -> [d]
mapleft = map
mapright :: (a -> b) -> [a] -> [b]
mapright = map
是什么了。为清楚起见,
让我们把两个(相同的)地图写成
mapright
现在,在Haskell中编码“两个变量的函数”的方式变得很重要。由于Haskell中的函数实际上只有一个输入,我们必须小心,如上所述。因此,a -> b
的域/输入实际上只是[a] -> [b]
类型,而输出实际上是(.)
类型。回到a1 -> b1
的签名,这意味着我们已将上面的右手操作数类型(a -> b) -> ([a] -> [b])
修改为a1 = a -> b
。因此,b1 = [a] -> [b]
和[a] -> [b] = b1 = c -> d
。
以左手操作数的相同方式进行,我们看到c = [a]
,d = [b]
和c1 = [c] -> [d] = [[a]] -> [[b]]
。同样的推理给出leftmap . rightmap = map . map
。
我们已经完成了,我们现在可以读出a1 -> c1 = (a -> b) -> [[a]] -> [[b]]
的类型:它是
Prelude> :t (map . map)
(map . map) :: (a -> b) -> [[a]] -> [[b]]
。这一点得到了GHCi的证实:
(map . map) not
现在很清楚为什么你谈到的两个功能是不同的。显然,[[Bool]] -> [[Bool]]
的类型为map not
,这正是您想要的。另一方面,[Bool] -> [Bool]
的类型为map not
。获取map
的输出并将其输入map
的(第一个)输入甚至不会进行类型检查:map not
的第一个输入必须是< em> function ,而[Bool]
的输出是{{1}}。
答案 2 :(得分:2)
函数应用程序绑定非常紧密,因此如果没有$
它会解析为map . (map not)
而不是(map . map) not
,这就是您想要的解释。