应用函子的`(<*>)`定义?

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类别理论术语是受欢迎的.

Car*_*arl 8

对于问题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给你的(<*>)背部类型,不是吗?


Jor*_*ano 5

1)(<*>)定义为liftA2,其中liftA2定义为(<*>).它是如何工作的?我看不到明显的"递归中断"案例......

这不是递归.在您的实例中,Applicative您既可以定义它们,也可以只定义一个.如果你定义(<*>)那么liftA2定义是(<*>),反之亦然.

2)id是一个a -> a功能.为什么它liftA2作为一个(a -> b -> c)函数传递?

统一的工作原理如下,

(<*>) :: f (a -> b) -> f a -> f b
(<*>) = liftA2 id

liftA2 :: (a -> b -> c) -> f a -> f b -> f c
Run Code Online (Sandbox Code Playgroud)
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.

liftA2 :: (a -> b -> c) -> f a -> f b -> f c
liftA2 h x = (<*>) (fmap h x)
Run Code Online (Sandbox Code Playgroud)

更名从第一个参数fh,以防止混淆,因为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)