Haskell 镜头:如何使 view 与 traverse 兼容?

12

我正在尝试通过在Haskell中实现来学习有关镜头的知识。我已经实现了如下所示的view组合器:

{-# LANGUAGE RankNTypes #-}

import Control.Applicative
import Data.Traversable

type Lens s a = Functor f => (a -> f a) -> s -> f s

view :: Lens s a -> s -> a
view lens = getConst . lens Const

然而,当我试图将其与traverse结合使用时,会出现以下错误消息:

Prelude> :load Lens.hs
[1 of 1] Compiling Main             ( Lens.hs, interpreted )
Ok, modules loaded: Main.
*Main> :t view traverse

<interactive>:1:6:
    Could not deduce (Applicative f) arising from a use of ‘traverse’
    from the context (Traversable t)
      bound by the inferred type of it :: Traversable t => t a -> a
      at Top level
    or from (Functor f)
      bound by a type expected by the context:
                 Functor f => (a -> f a) -> t a -> f (t a)
      at <interactive>:1:1-13
    Possible fix:
      add (Applicative f) to the context of
        a type expected by the context:
          Functor f => (a -> f a) -> t a -> f (t a)
        or the inferred type of it :: Traversable t => t a -> a
    In the first argument of ‘view’, namely ‘traverse’
    In the expression: view traverse

非常抱歉,我不理解这个错误信息。请说明它的含义以及如何修复它。

3个回答

10
作为其他答案已经解释的那样,问题在于`view`期望适用于任何`Functor f`的东西,但`traverse`仅在`f`也是`Applicative`时才有效(而有些functor不是applicative)。
在`lens`中,这个问题通过使`view`的类型不接受`Rank2`参数来解决(实际上,lens中的大多数函数都不使用Lens类型同义词,它们始终使用更弱的类型)。对于您的函数,观察到`view`只使用`f ~ Const`。这就是你可以将类型签名更改为:
view :: ((a -> Const a a) -> s -> Const a s) -> s -> a

实现可以保持不变,但现在view也适用于traverse

view traverse :: (Traversable t, Monoid a) => t a -> a

请注意额外的Monoid约束条件。这个约束条件是因为如果你在traverse :: (Traversable t, Applicative f) => (a -> f a) -> t a -> f (t a)中设置f ~ Const a,你需要一个Applicative (Const a)实例。该实例对a有一个Monoid约束条件。这也是有道理的,因为可遍历对象可能为空或包含多个元素,所以需要memptymappend

1
非常好的答案。非常感谢您为问题提供了一个聪明的解决方案。不过我有一个问题:view 的类型签名不会变成 ((a -> Const a a) -> s -> Const a s) -> s -> a 吗? - Aadit M Shah

3

traverse 具有以下类型:

traverse :: (Applicative f, Traversable t) => (x -> f y) -> t x -> f (t y)

如果我们将类型定义中的自由变量f明确化,那么它的定义实际上是:
type Lens s a = forall f . Functor f => (a -> f a) -> s -> f s

因此,view需要一个可以操作任何Functor的函数,而traverse只能操作一个Applicative

您可以通过在Lens的定义中将Functor更改为Applicative来简单地解决错误,但我不确定这是否完全符合您的要求。


2
将这里的 Functor 更改为 Applicative,那么你就会得到一个 Traversal,是吗?我想这可以让你对“Traversal”这个名称的来源有一些直觉。 - Tikhon Jelvis

3
根据你对 Lens 的定义,traverse 不能成为 Lens,因为 traverse 不适用于所有的 Functor
让我们看一下你的类型:
λ :set -XRankNTypes 
λ :m +Control.Applicative Data.Traversable 
λ type Lens s a = Functor f => (a -> f a) -> s -> f s
λ :t traverse
traverse
  :: (Applicative f, Traversable t) => (a -> f b) -> t a -> f (t b)

现在我们可以看到,从某种程度上讲,traverse比我们的Lens类型略为通用——它可以接受一个从a -> f b的函数,而我们的镜头只能接受从a -> f a的函数。
将其限制在这种情况下没有问题,因此我们可以说:
λ :t traverse :: (Traversable t, Applicative f) => (a -> f a) -> t a -> f (t a)
traverse :: (Traversable t, Applicative f) => (a -> f a) -> t a -> f (t a)
  :: (Applicative f, Traversable t) => (a -> f a) -> t a -> f (t a)

现在显然,如果 traverse 要成为一个 Lens,它必须是一个 Lens (t a) a,因为这是唯一使类型变量对齐的方法。

让我们试试这个方法。

λ :t traverse :: Lens (t a) a

<interactive>:1:1:
    Could not deduce (Traversable t1) arising from a use of `traverse'
    from the context (Functor f)
      bound by the inferred type of
               it :: Functor f => (a -> f a) -> t a -> f (t a)
      at Top level
    or from (Functor f1)
      bound by an expression type signature:
                 Functor f1 => (a1 -> f1 a1) -> t1 a1 -> f1 (t1 a1)
      at <interactive>:1:1-24
    Possible fix:
      add (Traversable t1) to the context of
        an expression type signature:
          Functor f1 => (a1 -> f1 a1) -> t1 a1 -> f1 (t1 a1)
        or the inferred type of
           it :: Functor f => (a -> f a) -> t a -> f (t a)
    In the expression: traverse :: Lens (t a) a

糟糕,它不喜欢那个。哦,等等,为了使用traverse,我们的类型t必须是Traversable,因此让我们添加该限制。(就像“可能的修复”建议的那样):

λ :t traverse :: Traversable t => Lens (t a) a

<interactive>:1:1:
    Could not deduce (Applicative f1) arising from a use of `traverse'
    from the context (Functor f, Traversable t)
      bound by the inferred type of
               it :: (Functor f, Traversable t) => (a -> f a) -> t a -> f (t a)
      at Top level
    or from (Traversable t1, Functor f1)
      bound by an expression type signature:
                 (Traversable t1, Functor f1) =>
                 (a1 -> f1 a1) -> t1 a1 -> f1 (t1 a1)
      at <interactive>:1:1-41
    Possible fix:
      add (Applicative f1) to the context of
        an expression type signature:
          (Traversable t1, Functor f1) =>
          (a1 -> f1 a1) -> t1 a1 -> f1 (t1 a1)
        or the inferred type of
           it :: (Functor f, Traversable t) => (a -> f a) -> t a -> f (t a)
    In the expression: traverse :: Traversable t => Lens (t a) a

好的,现在的问题是它无法推断fApplicative(使用traverse也需要),只能知道它是一个Functor(从Lens的定义中得到)。

然而,我们无法将Applicative f添加到上下文中 - 因为f被隐藏了。当我们说type Lens s a = Functor f => (a -> f a) -> s -> f s时,我们意思是Lens必须适用于所有Functor

但是,traverse仅适用于那些同时是ApplicativeFunctorFunctor的子集。因此,在这种情况下,traverse的类型比Lens允许的类型更具体


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接