在 Haskell 中,如何将嵌套上下文中的 "apply" 函数转换为上下文中的值?

In Haskell how to "apply" functions in nested context to a value in context?

nestedApply :: (Applicative f, Applicative g) => g (f (a -> b)) -> f a -> g (f b)

如类型所示,如何在上下文 f 中将 (a->b) 应用于 a

感谢您的帮助。

这是关注类型很有帮助的案例之一。我会尽量保持简单并解释原因。

让我们从描述任务开始。我们有 gfab :: g(f(a->b))fa :: f a,我们想要有 g(f b).

gfab :: g (f (a -> b))
fa   :: f a
??1  :: g (f b)

由于 g 是一个函子,要获得类型 g T 我们可以从 g U 类型的值 ??2 开始并将 fmap 应用于 ??3 :: U -> T。在我们的例子中,我们有 T = f b,所以我们正在寻找:

gfab :: g (f (a -> b))
fa   :: f a
??2  :: g U
??3  :: U -> f b
??1 = fmap ??3 ??2  :: g (f b)

现在,看来我们应该选择 ??2 = gfab。毕竟,这是我们拥有的类型 g Something 的唯一值。我们得到U = f (a -> b).

gfab :: g (f (a -> b))
fa   :: f a
??3  :: f (a -> b) -> f b
??1 = fmap ??3 gfab :: g (f b)

让我们把 ??3 变成一个 lambda,\ (x :: f (a->b)) -> ??4??4 :: f b。 (x的类型可以省略,但我决定加上它来说明是怎么回事)

gfab :: g (f (a -> b))
fa   :: f a
??4  :: f b
??1 = fmap (\ (x :: f (a->b)) -> ??4) gfab :: g (f b)

如何制作??4。好吧,我们有 f (a->b)f a 类型的值,所以我们可以 <*> 得到 f b。我们最终得到:

gfab :: g (f (a -> b))
fa   :: f a
??1 = fmap (\ (x :: f (a->b)) -> x <*> fa) gfab :: g (f b)

我们可以将其简化为:

nestedApply gfab fa = fmap (<*> fa) gfab

现在,这不是最优雅的方法,但理解这个过程很重要。

nestedApply :: (Applicative f, Applicative g) 
            => g (f (a -> b))  
            ->    f  a 
            -> g (f       b )

要在上下文 f 中将 (a->b) 应用于 a,我们需要在上下文 中操作 g.

那只是 fmap

翻转签名更清晰,重点看最后部分

flip nestedApply :: (Applicative f, Applicative g) 
            =>    f  a 
            -> g (f (a -> b))     --- from here
            -> g (f       b )     --- to here

所以我们这里有

nestedApply gffun fx = fmap (bar fx) gffun

下应用 bar fx g fmap 为我们包装。这是

bar fx ::         f (a -> b)
            ->    f       b   

bar ::            f  a
            ->    f (a -> b)
            ->    f       b   

这只是 <*> 不是吗,又翻转了。这样我们就得到了答案,

nestedApply gffun fx  =  fmap (<*> fx) gffun

我们可以看到只使用了gfmap个功能,所以我们只需要

nestedApply :: (Applicative f, Functor g) => ...

在类型签名中。


写在sheet纸上容易,二维.我们在这里用狂野的缩进来模仿,以获得垂直对齐。

是的,我们人类首先学会了书写,在纸上,然后在打字机上打字,时间要晚得多。上一代或两代人从小就被迫使用现代设备进行线性打字,但 现在 涂鸦和说话(以及手势和指点)有望再次流行起来。创造性的输入模式最终将包括 3D 工作流程, 将是一个明确的进步。 1D 不好,2D 好,3D 更好。例如,许多类别理论图在以 3D 形式绘制时更容易理解(至少可以想象)。经验法则是,它应该简单,而不是困难。如果太忙,可能需要另一个维度。

只是玩连接电线。几张不言而喻的图,就大功告成了。


这里有一些类型的曼荼罗(再次翻转):

-- <$>               -- <*>               -- =<<
f     a              f     a              f     a       
     (a -> b)        f    (a -> b)             (a -> f b)
f          b         f          b         f    (     f b)   -- fmapped, and
                                          f            b    -- joined

当然还有所有应用程序之母,

--      $
      a
      a -> b
           b

a.k.a。 Modus Ponens (是的,也翻转了).