Ser*_*bov 6 haskell applicative
一些Haskell源代码(参见参考资料):
-- | Sequential application.
--
-- A few functors support an implementation of '<*>' that is more
-- efficient than the default one.
(<*>) :: f (a -> b) -> f a -> f b
(<*>) = liftA2 id
-- | Lift a binary function to actions.
--
-- Some functors support an implementation of 'liftA2' that is more
-- efficient than the default one. In particular, if 'fmap' is an
-- expensive operation, it is likely better to use 'liftA2' than to
-- 'fmap' over the structure and then use '<*>'.
liftA2 :: (a -> b -> c) -> f a -> f b -> f c
liftA2 f x = (<*>) (fmap f x)
Run Code Online (Sandbox Code Playgroud)
有三件事让我感到困惑:
1)(<*>)
定义为liftA2
,其中liftA2
定义为(<*>)
.它是如何工作的?我看不到明显的"递归中断"案例......
2)id
是一个a -> a
功能.为什么它liftA2
作为一个(a -> b -> c)
函数传递?
3)fmap id x
总是等于x
,因为仿函数必须保留适当的身份.因此(<*>) (fmap id x)
= (<*>) (x)
where x
= f a
- 一个有问题的a
仿函数本身(顺便说一下,如何a
从纯类别理论的角度解释仿函数的仿真?仿函数只是一个类别之间的映射,它没有进一步的"典型化". ..似乎更好的说 - "一个a
带有(endo)仿函数的容器,为每个Hask
明确定义的Haskell类型的asummed类的实例定义.)因此,(<*>) (f a)
虽然定义(<*>)
期望f(a' -> b')
:因此,唯一的方法是工作是故意a
成为一个(a' -> b')
.然而,当我跑:t \x -> (<*>) (fmap id x)
进去的时候gchi
,它会吐出令人兴奋的东西:f (a -> b) -> f a -> f b
- 我无法解释.
有人可以一步一步解释这是如何工作的以及它为什么编译?如果需要,PS类别理论术语是受欢迎的.
对于问题1,您遗漏了一个非常重要的背景.
class Functor f => Applicative f where
{-# MINIMAL pure, ((<*>) | liftA2) #-}
Run Code Online (Sandbox Code Playgroud)
您引用的那些定义属于一个类.这意味着实例可以覆盖它们.此外,MINIMAL编译指示说,为了工作,必须在实例中至少覆盖其中一个.因此,只要在特定实例中覆盖了递归,就会发生递归的破坏.这就像Eq
类定义的方式(==)
和(/=)
彼此的方式一样,因此您只需要在手写实例中为一个定义提供定义.
问题二,a -> b -> c
是简写a -> (b -> c)
.因此,它与统一(让我们重命名变量,以避免碰撞)d -> d
的(b -> c) -> (b ->c)
.(切向地,这也是类型($)
.)
对于三个人 - 你是绝对正确的.继续简化!
\x -> (<*>) (fmap id x)
\x -> (<*>) x
(<*>)
Run Code Online (Sandbox Code Playgroud)
所以它真的不应该是一个惊喜ghci给你的(<*>)
背部类型,不是吗?
1)
(<*>)
定义为liftA2
,其中liftA2
定义为(<*>)
.它是如何工作的?我看不到明显的"递归中断"案例......
这不是递归.在您的实例中,Applicative
您既可以定义它们,也可以只定义一个.如果你定义(<*>)
那么liftA2
定义是(<*>)
,反之亦然.
2)
id
是一个a -> a
功能.为什么它liftA2
作为一个(a -> b -> c)
函数传递?
统一的工作原理如下,
Run Code Online (Sandbox Code Playgroud)(<*>) :: f (a -> b) -> f a -> f b (<*>) = liftA2 id liftA2 :: (a -> b -> c) -> f a -> f b -> f c
id : u -> u
liftA2 : (a -> (b -> c) -> f a -> f b -> f c
------------------------------------------------------
u = a
u = b->c
id : (b->c) -> (b->c)
liftA2 : ((b->c) -> (b->c)) -> f (b->c) -> f b -> f c
------------------------------------------------------
liftA2 id : f (b->c) -> f b -> f c
Run Code Online (Sandbox Code Playgroud)
3.
Run Code Online (Sandbox Code Playgroud)liftA2 :: (a -> b -> c) -> f a -> f b -> f c liftA2 h x = (<*>) (fmap h x)
更名从第一个参数f
来h
,以防止混淆,因为f
还示出了在型
h :: a -> (b -> c)
x :: f a
fmap :: (a -> d) -> f a -> f d
------------------------------
d = b -> c
h :: a -> (b->c)
x :: f a
fmap :: (a -> (b->c)) -> f a -> f (b->c)
----------------------------------------
fmap h x :: f (b -> c)
fmap h x :: f (b -> c)
(<*>) :: f (b -> c) -> f b -> f c
-------------------------------------
(<*>) fmap h x :: f b -> f c
Run Code Online (Sandbox Code Playgroud)
编辑:
一致性
为了显示两个公式的一致性,首先让我们先重写liftA2
一些更简单的东西.我们可以使用下面的公式来摆脱fmap
和使用pure
和<*>
fmap h x = pure h <*> x
Run Code Online (Sandbox Code Playgroud)
并且最好将所有点放在定义中.所以我们得到了,
liftA2 h u v
= (<*>) (fmap h u) v
= fmap h u <*> v
= pure h <*> u <*> v
Run Code Online (Sandbox Code Playgroud)
所以我们要检查一致性,
u <*> v = liftA2 id u v
liftA2 h u v = pure h <*> u <*> v
Run Code Online (Sandbox Code Playgroud)
首先,我们需要属性 pure id <*> u = u
u <*> v
= liftA2 id u v
= pure id <*> u <*> v
= u <*> v
Run Code Online (Sandbox Code Playgroud)
对于第二个,我们需要一个属性liftA2
.申请人的属性通常以方式给出pure
,<*>
因此我们需要首先推导出它.所需的公式来源于pure h <*> pure x = pure (h x)
.
liftA2 h (pure x) v
= pure h <*> pure x <*> v
= pure (h x) <*> v
= liftA2 (h x) v
Run Code Online (Sandbox Code Playgroud)
这需要h : t -> a -> b -> c
.一致性证明成为,
liftA2 h u v
= pure h <*> u <*> v
= pure h `liftA2 id` u `liftA2 id` v
= liftA2 id (liftA2 id (pure h) u) v
= liftA2 id (liftA2 h u) v
= liftA2 h u v
Run Code Online (Sandbox Code Playgroud)