Haskell - 评价 (+) <$> (+3) <*> (*100) $ 5
Haskell - Evaluation of (+) <$> (+3) <*> (*100) $ 5
From the chapter on Functors in Learn You a Haskell for Great Good,Lipovača 指出:
"When we do (+) <$> (+3) <*> (*100)
, we're making a function that will use +
on the results of (+3)
and (*100)
and return that. To demonstrate on a real example, when we did (+) <$> (+3) <*> (*100) $ 5
, the 5
first got applied to (+3)
and (*100)
, resulting in 8
and 500
. Then, +
gets called with 8
and 500
, resulting in 508
."
但是,如果我尝试自己评估函数,请考虑函子 ((->) r) 上 Applicative 的定义:
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)
考虑到我们在这种情况下的函子是 ((->) r),我可以推断这是在之前的评估中发生的转换(假设左结合性首先发生,而不是右结合应用5
):
(\x -> a + b)
其中 a
= (+ 3)
和 b
= (* 100)
。这是应该返回的函数。但是,我假设这是最终(粗略)形式是否正确?
(\x -> (3 + x) + (100 * x)) $ 5
...产生 508。
我发现 Lipovača 在表达式如何工作方面的描述更易于理解,但我的直觉告诉我,Haskell 编译器引擎盖下的血淋淋的细节并不完全正确。我更容易认为 (+) 的 fmap 首先发生,导致一个函数有两个函子,这两个函子是部分应用的函数,接受共享输入,然后我们向它应用一个值。我们可以这样做是因为惰性求值。这是错误的吗?
首先,注意<$>
和<*>
都关联到左边。内部没有发生任何神奇的事情,我们可以看到本质上是一系列 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
From the chapter on Functors in Learn You a Haskell for Great Good,Lipovača 指出:
"When we do
(+) <$> (+3) <*> (*100)
, we're making a function that will use+
on the results of(+3)
and(*100)
and return that. To demonstrate on a real example, when we did(+) <$> (+3) <*> (*100) $ 5
, the5
first got applied to(+3)
and(*100)
, resulting in8
and500
. Then,+
gets called with8
and500
, resulting in508
."
但是,如果我尝试自己评估函数,请考虑函子 ((->) r) 上 Applicative 的定义:
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)
考虑到我们在这种情况下的函子是 ((->) r),我可以推断这是在之前的评估中发生的转换(假设左结合性首先发生,而不是右结合应用5
):
(\x -> a + b)
其中 a
= (+ 3)
和 b
= (* 100)
。这是应该返回的函数。但是,我假设这是最终(粗略)形式是否正确?
(\x -> (3 + x) + (100 * x)) $ 5
...产生 508。
我发现 Lipovača 在表达式如何工作方面的描述更易于理解,但我的直觉告诉我,Haskell 编译器引擎盖下的血淋淋的细节并不完全正确。我更容易认为 (+) 的 fmap 首先发生,导致一个函数有两个函子,这两个函子是部分应用的函数,接受共享输入,然后我们向它应用一个值。我们可以这样做是因为惰性求值。这是错误的吗?
首先,注意<$>
和<*>
都关联到左边。内部没有发生任何神奇的事情,我们可以看到本质上是一系列 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