如何为特定类型实现像 Getter 这样的 Lens?

How to implement a Lens like Getter for a specific type?

给定一般Getter类型

type Getter s a = forall f. (Contravariant f, Functor f) => (a -> f a) -> s -> f s

我如何为一对元组实现 getter?我想上面的类型代表了一个部分应用的函数,而缺失的部分正是让我感到困惑的地方。

除此之外我不理解逆变约束。它可能是为了使类型更像镜头,但函子还不够吗?

在类型 Getter s a 中,类型 s 表示 "object" 以某种方式 "contains" 类型 a 的值,其中 getter 可以从中得到 "extract"。

如果您想为一对实施 getter,那么您的 s = (x, y)a 将是 xy,取决于您要提取的元素。为清楚起见,假设您要提取第一个元素。然后 a = x.

好的,您的函数将如下所示:

firstElementGetter :: Getter (x, y) x

现在,如果我们展开 Getter 的定义,我们得到:

firstElementGetter :: (blah-blah) => (x -> f x) -> (x, y) -> f (x, y)
firstElementGetter h (x, y) = ...

这意味着你的函数有两个参数:(1) 一个函数 h 可以 "wrap" 一个 x in functor f,和 (2) 一个元组(x, y);它需要 return 一个包含在仿函数 f 中的元组 (x, y)。让我们看看能不能做到。

首先,我们有一个函数 h,它接受类型为 x 的参数。方便的是,我们也有那种类型的 x。让我们应用它:h x。结果的类型为 f x。我们怎样才能把它变成 f (x, y)

好吧,Functor 的真正意义在于您可以 map 覆盖它。那么我们可以通过什么函数映射f x得到f (x, y)呢?这样的函数显然需要有一个类型 x -> (x, y) - 瞧!我们拥有构建这样一个函数的所有部分!我们可以将现有的 y 重新组合到元组中:\xx -> (xx, y)

现在我们已经完成了 getter 的合同:

firstElementGetter :: (blah-blah) => (x -> f x) -> (x, y) -> f (x, y)
firstElementGetter h (x, y) = fmap (\xx -> (xx, y)) (h x)

从根本上说,这就是所有光学器件的工作原理 - 无论是 getters、遍历、棱镜还是其他任何东西。然后,消费者可以通过选择正确的函子 f 和正确的包装函数 h.

让他们做不同的事情

例如,消费者可以使用您的 getter 到 "extract" 元组中的第一个元素,方法是选择此函子:

data Const a b = Const a

instance Functor (Const a) where
    fmap f (Const a) = Const a

注意它是如何完全忽略类型 b 的。实际上 "wrap" 不是它的值,而且 fmap 实现也没有触及它。您可能会说它是一个 "fake" 函子。我们将利用它来发挥我们的优势!

对于函数 h,我们将选择 Const。它符合类型,因为Const :: x -> Const x foo对于任何foo,它恰好与x -> Const x x兼容,它在f = Const x时匹配所需的类型x -> f x。我知道,这有点令人兴奋,但请耐心等待。

现在,如果 h = Const,我们的 getter 将尽职尽责地调用 h x,这将 return Const x,getter 将fmap 结束了,但是由于我们对 fmap 的定义忽略了它的第一个参数,因此 fmap 的结果仍然与 Const x 完全相同,而 getter 将然后 return。现在,我们需要做的就是打开它,我们就完成了!

getFirst :: (x, y) -> x
getFirst pair = 
    let (Const x) = firstElementGetter Const pair
    in x

Contravariant 部分是一个有点聪明的类型级 hackery。看,当函子 f 不仅是 Functor,而且是 Contravariant 时,它 必须 具有 Const 的形状在上面输入 - 即它不能 "wrap" 里面的类型参数的值。

Functor 可以被认为是 "produces"(或 "contains")值的东西,而 Contravariant 是 "consumes" 值的东西。如果 "wrapper" 类型必须是两者,实现它的唯一方法是只假装 "consume" 或 "produce" 值,但在幕后忽略它们。我知道这不是一个非常清楚的解释,但我不能做得更好。只需尝试实现这样的类型,您就会看到。

所以 Getter 被赋予了这个奇怪的约束,只是为了确保它唯一能做的是 "get" 一个值,而不是 "set" 或 "transform"它。

  • getter 的最简单实现就是 s -> a
  • 一个不那么简单的实现是 (a -> x) -> s -> x - 在连续传递风格中,但等同于前一个。
  • 一个不太简单的实现是 (a -> Const x a) -> s -> Const x s - 我只是用 Const x foo 替换了 a 和不同的 foo,但它仍然等同于前一个。

虽然第一个(最简单的)定义可以用于实际获取,但最后一个定义的优点是具有与其他镜头兼容的签名,因此可以在镜头组合中使用这种 getter。