【问题标题】:Confusion regarding composition in Haskell关于 Haskell 中的组合的困惑
【发布时间】:2019-05-12 12:19:46
【问题描述】:

我正在阅读 Haskell 的书,我意识到我很难理解函数组合。在一个非常基本的层面上,我有一个管道的心理模型,它接受输入并将其结果传递给组合中的下一个函数。对于简单的功能,这很容易。

我遇到困难的地方是理解组成函数的结果类型签名是如何形成的。例如,如果我们查看elem 的基本定义:

elem :: (Foldable t, Eq a) => a -> t a -> Bool
elem = any . (==)

>:t (==)
(==) :: Eq a => a -> a -> Bool
>:t any
any :: Foldable t => (a -> Bool) -> t a -> Bool

我看不到生成的类型签名是如何发生的。如果给我这个函数并要求我写类型签名,我会绝望地迷路。

以下内容也是如此。在 Traversable 一章中,我们被告知 traverse 只是 sequenceAfmap 组成:

traverse :: (Traversable t, Applicative f) => (a -> f b) -> t a -> f (t b)
traverse f = sequenceA . fmap f

>:t fmap
fmap :: Functor f => (a -> b) -> f a -> f b
>:t sequenceA
sequenceA :: (Traversable t, Applicative f) => t (f a) -> f (t a)

我自己理解每个函数的类型签名,但是它们如何组合来创建traverse 的类型签名?

在这里超级迷路,任何帮助将不胜感激。

【问题讨论】:

  • 不知道如何解释这些特定的例子,但一般来说,我认为你应该练习 η-expanding/reducing 表达式与组合,以感受它。
  • 请记住,(a -> Bool) 也可以用类似b 的类型变量来表示,这意味着您可以将a -> a -> Bool 视为a -> b,将(a -> Bool) -> t a -> Bool 视为b -> t a -> Bool。通过将t a -> Bool 命名为c 进一步扩展,(a -> Bool) -> t a -> Bool 变为b -> c。所以总的来说,这两者很容易组合。
  • sequenceA 不是fmap组成的;它由fmap f 组成。区别很重要。
  • 为了更好地理解事物是如何构成的,请尝试扩展定义:elem = any . (==) (.) 的定义等于 elem = \x -> any ((==) x),它可以是“eta-expanded”(将两边缺少的参数和参数)添加到elem = \x xs -> any ((==) x) xs中,我希望其含义更明显。加上一点语法糖,这是elem x xs = any (x ==) xs

标签: haskell


【解决方案1】:

也许仅仅在视觉上对齐类型会让你对管道的进展有一些直觉,并帮助你朝着下一个困惑点前进!

(==)       :: Eq a => a -> (a -> Bool)
any        ::              (a -> Bool) -> (t a -> Bool)
any . (==) :: Eq a => a                -> (t a -> Bool)

为了让下一个屏幕保持在一个屏幕上,我们将Traversable 缩写为T,将Applicative 缩写为A。您的问题中还有两个 fs,一个在计算级别,一个在类型级别。为避免混淆,我将把您的计算级别 f 重命名为 g。所以如果g :: a -> f b 换了一些Applicative f:

fmap g                   ::  T t       =>               t a -> t (f b)
sequenceA                :: (T t, A f) =>                      t (f b) -> f (t b)
sequenceA . fmap g       :: (T t, A f) =>               t a            -> f (t b)
\g -> sequenceA . fmap g :: (T t, A f) => (a -> f b) -> t a            -> f (t b)

(等等!fmap g 怎么来,t 的约束是Traversable 而不是Functor?好吧,没问题:我们实际上可以给它更宽松的类型fmap g :: Functor t => ...。但是因为每个Traversable 必须是Functor,也可以给这个类型,这样比较清楚。)

【讨论】:

  • Applicative f :: a -> f b ?那里很奇怪f。它是什么? > 我们是否可以减轻认知负担,或者将g 用于其中之一?
  • @WillNess 好的。你说服了我。 =)
  • 我是来服务的。 :) 顺便说一句,对齐类型的荣誉。多年来一直这样做,我自己....
  • 最后一件事:可能值得在fmap :: ... t a -> t (f b) 行上方添加g :: A f => a -> f b 行,以获得更多视觉上对齐的内容。供您考虑。
  • @WillNess g 在类型推断中的作用与其他术语相比有点倒退,因为稍后将对其进行 lambda 抽象。我试图在我的回答中巧妙地处理这一点(例如,我为某些 Applicative f 写了有点尴尬的“g :: a -> f b”而不是“g :: Applicative f => a -> f b”)。在类型签名序列中写g :: A f => a -> f b 在这个意义上肯定是不精确的;因此,与您之前的建议不同,这不仅与演示有关,而且与正确性有关。而且我不会把g :: exists f a b. A f *> a -> f b...
【解决方案2】:

All Haskell functions take just one argument——即使是我们经常认为的接受多个参数的那些。考虑您的 elem 示例:

elem :: (Foldable t, Eq a) => a -> t a -> Bool
elem = any . (==)

>:t (==)
(==) :: Eq a => a -> a -> Bool
>:t any
any :: Foldable t => (a -> Bool) -> t a -> Bool

(==) 的类型可以读作(==) :: Eq a => a -> (a -> Bool):它需要一个a 值(a 可以是任何Eq 的实例)并给出一个a -> Bool 函数。反过来,any 接受一个 a -> Bool 函数(a 可以是任何东西)并给出一个 t a -> Bool 函数(t 可以是任何 Foldable 的实例)。既然如此,any . (==) 管道中的中间类型是Eq a => a -> Bool

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-04-08
    • 2018-04-10
    • 1970-01-01
    • 2019-05-17
    • 2020-03-06
    • 2016-03-16
    • 2013-07-27
    相关资源
    最近更新 更多