From the chapter on Functors in Learn You a Haskell for Great Good,Lipovača说:
“当我们执行
(+) <$> (+3) <*> (*100)
时,我们正在制作一个函数,对+
和(+3)
的结果使用(*100)
并返回该函数。例如,当我们(+) <$> (+3) <*> (*100) $ 5
时,5
首先应用于(+3)
和(*100)
,结果为8
和500
。然后,使用+
和8
调用500
,结果为508
。“
但是,如果我尝试自己评估函数,考虑到函数的这个定义(( - &gt;)r):
instance Applicative ((->) r) where
pure x = (\_ -> x)
f <*> g = \x -> f x (g x)
我将上述表达式的评估读作:
(\x -> (3 + x) (100 * x)) $ 5
但是我没有看到我们如何将两个部分应用的二进制函数组合成一个lambda(实际上,GHCi会在尝试将其绑定到变量时抛出无限类型错误)。此外,对于工作解释,如果我们查看<$>
的类型定义,我们得到:
(<$>) :: Functor f => (a -> b) -> f a -> f b
或者更具体地说,我们可以将其提升为:
(<$>) :: Functor f => (a -> b) -> (f a -> f b)
考虑到在这种情况下我们的仿函数是(( - &gt;)r),我可以推断这是在先前的评估中发生的变换(假设左关联性首先发生,而不是{的正确的关联应用程序{1}}):
5
其中(\x -> a + b)
= a
和(+ 3)
= b
。这是应该返回的函数。但是,如果我认为这是最终(粗略)形式,我是否正确?
(* 100)
......产生508。
我发现Lipovača的描述在表达式如何工作方面更容易理解,但我的直觉告诉我,对于Haskell编译器引擎盖下的gorey细节并不完全正确。我更容易认为(+)的fmap首先发生了一个带有两个仿函数的函数,这两个仿函数是部分应用的函数,它们接受共享输入,然后我们为它应用了一个值。我们可以这样做,因为懒惰的评估。这是错的吗?
答案 0 :(得分:16)
首先请注意,<$>
和<*>
都与左侧相关联。在内部没有任何神奇的事情发生,我们可以看到转换基本上是一系列的eta扩展和beta减少。一步一步,它看起来像这样:
(((+) <$> (+3)) <*> (*100)) $ 5 -- Add parens
((fmap (+) (+3)) <*> (*100)) $ 5 -- Prefix fmap
(((+) . (+3)) <*> (*100)) $ 5 -- fmap = (.)
((\a -> (+) ((+3) a)) <*> (*100)) $ 5 -- Definition of (.)
((\a -> (+) (a+3)) <*> (*100)) $ 5 -- Infix +
((\a b -> (+) (a+3) b)) <*> (*100)) $ 5 -- Eta expand
(\x -> (\a b -> (+) (a+3) b) x ((*100) x)) $ 5 -- Definition of (<*>)
(\x -> (\a b -> (+) (a+3) b) x (x*100)) $ 5 -- Infix *
(\a b -> (+) (a + 3) b) 5 (5*100) -- Beta reduce
(\a b -> (a + 3) + b) 5 (5*100) -- Infix +
(5 + 3) + (5*100) -- Beta reduce (twice)
508 -- Definitions of + and *
有点令人困惑的是,$
关联到右边的事实与这里发生的事情关系不大,而事实是它的固定性为0.我们可以看到这个,如果我们定义一个新的运算符:
(#) :: (a -> b) -> a -> b
f # a = f a
infixl 0 #
和GHCi:
λ> (+) <$> (+3) <*> (*100) # 5
508