【问题标题】:Derive Haskell type and implementation of (<*>)(<*>)派生 Haskell 类型和 (<*>)(<*>) 的实现
【发布时间】:2014-12-22 12:56:14
【问题描述】:

我是新手,刚开始学习 Haskell,所以如果我提出愚蠢的问题,请多多包涵。

最近我在 SO 中遇到了一些问题,演示了如何推导函数和表达式的类型和实现(例如

How can I understand "(.) . (.)"?

&

Haskell function composition, type of (.)(.) and how it's presented )

我觉得答案很鼓舞人心

然后,我尝试为自己设计一些练习,以确保我知道如何应用这些技巧。

然后我自己想出了这个表达式:(&lt;*&gt;)(&lt;*&gt;),我不知道如何解决。

在 GHCi 中,它给出的类型签名为:

(<*>)(<*>) :: Applicative f => (f (a -> b) -> f a) -> f (a -> b) -> f b

但我的问题是如何从

开始
(<*>) :: Applicative f => f (a -> b) -> f a -> f b

并导出 GHCi 给出的类型签名?

另外,基于类型签名,(&lt;*&gt;)(&lt;*&gt;) = ??的实现会是怎样的?

我被卡住了,无法通过重新排列术语等技术解决这个问题。我什至不知道从哪里开始。

有人可以帮我吗?

非常感谢

注意**:(&lt;*&gt;)(&lt;*&gt;)这个表达式确实没有什么特殊含义,只是我自己随机想出来的一个练习

【问题讨论】:

  • 我要感谢大家及时提供高质量的答案,所有答案都很棒且鼓舞人心,他们拯救了我的日子。我真的很难选择一个。我希望我能接受多个答案。

标签: haskell types functional-programming


【解决方案1】:

首先,我们用两组不相交的类型变量写出(&lt;*&gt;)的类型:

(<*>) :: (Applicative f) => f (a -> b) -> f a -> f b
(<*>) :: (Applicative g) => g (c -> d) -> g c -> g d

如果我们想用第二个和第一个作为参数,我们需要

g (c -> d) ~ f (a -> b) -> f a -> f b

屈服

g ~ (f (a -> b) ->)    or,   ((->) (f (a -> b)) )
c ~ f a
d ~ f b

并注意g 的选择确实是一个应用函子:它是非新类型的Reader (f (a -&gt; b)) 应用程序。

所以应用程序有类型

g c -> g d

我们现在知道的

(f (a -> b) -> f a) -> (f (a -> b) -> f b)

q.e.d.

【讨论】:

  • 太棒了,我永远不会想到 ((->) (f(a -> b)) ) 的把戏。辉煌
  • 是否可以举个例子来说明它的使用?也许甚至是一个有用的案例?
【解决方案2】:

作为一个练习,将其缩小到函数,其中&lt;*&gt; 只是一个S-组合子,

Prelude> let s f g x = f x (g x)

Prelude> :t s s
s s :: ((t -> t1 -> t2) -> t -> t1) -> (t -> t1 -> t2) -> t -> t2
--               g2          g4                 g2          g1
--         g3                             g3
--                      g5
--                                  g6

重要的是要知道类型中的箭头关联到右侧,所以((t -&gt; t1 -&gt; t2) -&gt; t -&gt; t1) 实际上是((t -&gt; t1 -&gt; t2) -&gt; (t -&gt; t1)),而(t -&gt; t1 -&gt; t2)(t -&gt; (t1 -&gt; t2))

实现:

g6 g5 g3 t :: t2 
  g3 t t1 :: t2          -- need t1
  g5 g3 t :: t1
  g3 t (g5 g3 t) :: t2   -- so, 

h f g x = g x (f g x)    

所以,当我们有类型时,可以说是连接电线,或者将乐高积木拼在一起。我们尝试查看给定实体(参数)的哪些组合具有什么类型,如果它们对获得所需的输出类型有用(t2,上文),则在进一步的组合中使用它们。

同样,

Prelude Control.Applicative> :t (<*>) (<*>)
(<*>) (<*>) :: (Applicative f) =>
               (f (a -> b) -> f a) -> f (a -> b) -> f b
--                         f          fg
Prelude Control.Applicative> :t let axa f fg = fg <*> f fg in axa
let axa f fg = fg <*> (f fg) in axa :: (Applicative f) =>
                                       (f (a -> b) -> f a) -> f (a -> b) -> f b

【讨论】:

  • 感谢您的额外灵感,我在 SO 中关于组合器的各种帖子中遇到过几次,但我永远无法接受。你有什么文章/博客/材料可以推荐给学习组合器吗?
  • Wikipedia(及其链接文章)很好。至于一本书,对我来说是Davie
  • 组合符号实际上比 lambda 表达式更容易操作,imo。 Haskell 遵循组合风格。 :)
  • 感谢您的指点,我会看看他们。我也喜欢你的组合方法,我也会尝试接受它。圣诞快乐。
【解决方案3】:

此外,基于类型签名,如何实现 (&lt;*&gt;)(&lt;*&gt;) = ?? 会是?

颠倒顺序更容易(先找到实现,然后派生出它的类型)。

函数的应用实例是

instance Applicative ((->) a) where
    pure = const
    (<*>) f g x = f x (g x)

因此,只需将上述定义中的&lt;*&gt; 替换为f(&lt;*&gt;) (&lt;*&gt;) 就是\g x -&gt; x &lt;*&gt; g x。或者经过 alpha 转换后 \g h -&gt; h &lt;*&gt; g h.

由于(&lt;*&gt;) 的第一个参数是h,对于某些ab 必须是h :: f (a -&gt; b) 类型,其中fApplicative 中。

由于g 接收h 作为参数,它必须是g :: f (a -&gt; b) -&gt; c 类型,对于某些c

由于(&lt;*&gt;)的第一个参数h的类型是f (a -&gt; b),而(&lt;*&gt;)的第二个参数是g h,所以它的类型一定是g h :: f a,因为这样

(<*>) :: f (a -> b) -> f a -> f b

由于g :: f (a -&gt; b) -&gt; cg h :: f a

h :: f (a -> b)
g :: f (a -> b) -> c
g h :: f a               ,  c ~ f a   (!)

由于h 的类型为f (a -&gt; b)h &lt;*&gt; (whatever :: f a) 的类型为f b

总的来说:

h         :: f (a -> b)
g         :: f (a -> b) -> f a
h <*> g h :: f b

所以我们有

\g h -> h <*> g h :: (f (a -> b) -> f a) -> f (a -> b) -> f b

【讨论】:

  • 感谢您分享解决此问题的另一条路线,这很鼓舞人心。同时,我想在你的回答中澄清一点。您首先为应用类选择实例((->)a),我不确定您如何得出结论并选择该实例作为开始。你能告诉我吗?谢谢
  • @gatek,简单来说,(&lt;*&gt;) 作用于(&lt;*&gt;),后者是一个函数,所以你可以选择一个对应函数的实例,不用管。或者你可以把f (a -&gt; b) -&gt; f a -&gt; f b读成(-&gt;) (f (a -&gt; b)) (f a -&gt; f b)
  • 感谢您的快速回复。我很感激。我希望我可以在这里接受多个答案,因为我认为您已经展示了一个有趣的替代方案来解决我的问题。圣诞快乐
【解决方案4】:

所以我们有了函数

(<*>) :: Applicative f => f (a -> b) -> f a -> f b

并应用于它&lt;*&gt; :: Applicative g =&gt; g (c -&gt; d) -&gt; g c -&gt; g d(我替换了类型变量,因为(&lt;*&gt;)(&lt;*&gt;)的内部和外部&lt;*&gt;具有不同的含义。如果我们将第二个&lt;*&gt;应用于第一个&lt;*&gt;,那么第二个@ 987654327@ 必须是 f (a -&gt; b) 类型。所以我们有以下类型等式:

g (c -> d) -> g c -> g d = f (a -> b)

但是g (c -&gt; d) -&gt; g c -&gt; g d(-&gt;) (g (c -&gt; d)) (g c -&gt; g d) 的语法糖。所以:

(->) (g (c -> d)) (g c -> g d) = f (a -> b)

双方的类型构造函数和参数类型必须相等,所以:

(->) (g (c -> d)) = f

(g c -> g d) = (a -> b)

这意味着a = g cb = g d

现在我们可以看到第一个(&lt;*&gt;)的返回值为

f a -> f b = ((->) (g (c -> d)) (g c)) -> ((->) (g (c -> d)) (g d))
           = (g (c -> d) -> g c) -> (g (c -> d) -> g d)

这与(f (a -&gt; b) -&gt; f a) -&gt; f (a -&gt; b) -&gt; f b 模重命名变量相同。

此示例中的棘手部分是 (-&gt;) (g (c -&gt; d)) 是 Applicative(与任何其他 (-&gt;) e 相同)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-08-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多