如何为特定类型实现类似Getter的镜头?

bob*_*bob 3 haskell lenses haskell-lens

给定一般Getter类型

type Getter s a = forall f. (Contravariant f, Functor f) => (a -> f a) -> s -> f s
Run Code Online (Sandbox Code Playgroud)

我将如何为一对元组实现一个吸气剂?我想上面的类型代表了部分应用的功能,而缺少的部分正是让我感到困惑的地方。

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

Fyo*_*kin 7

在type中Getter s a,type s表示以某种方式“包含” type值的“对象”,agetter可以以某种方式“提取”该值。

如果要为一对实现一个吸气剂,则您s = (x, y)和的a取值为xy,具体取决于要提取的元素。为了清楚起见,我们要提取第一个元素。然后a = x

好的,因此您的函数应如下所示:

firstElementGetter :: Getter (x, y) x
Run Code Online (Sandbox Code Playgroud)

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

firstElementGetter :: (blah-blah) => (x -> f x) -> (x, y) -> f (x, y)
firstElementGetter h (x, y) = ...
Run Code Online (Sandbox Code Playgroud)

这意味着您的函数有两个参数:(1)h可以“包装” x函子的函数f,和(2)元组(x, y);它需要返回(x, y)包装在函子中的元组f。让我们看看是否可以做到这一点。

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

好吧,a的本质Functor是您可以映射它。那么我们可以映射f x得到什么功能f (x, y)呢?这样的功能显然需要一个类型x -> (x, y)-瞧!我们拥有构造此功能的所有部分!我们可以利用现有的y并将其重组为元组:\xx -> (xx, y)

现在,我们拥有履行吸气剂合同的一切:

firstElementGetter :: (blah-blah) => (x -> f x) -> (x, y) -> f (x, y)
firstElementGetter h (x, y) = fmap (\xx -> (xx, y)) (h x)
Run Code Online (Sandbox Code Playgroud)

从根本上讲,这就是所有光学器件的工作方式-它们是吸气剂,导线,棱镜还是其他。然后,消费者可以通过选择合适的函子f和合适的包装功能,使他们做不同的事情h

例如,消费者可以通过选择此函子,使用您的吸气剂从元组中“提取”第一个元素:

data Const a b = Const a

instance Functor (Const a) where
    fmap f (Const a) = Const a
Run Code Online (Sandbox Code Playgroud)

注意它是如何完全忽略类型的b。实际上并没有“包装”它的值,fmap实现也没有涉及它。您可能会说这是“伪”函子。我们将利用它来发挥我们的优势!

对于功能,h我们将选择Const。它适合该类型,因为Const :: x -> Const x foo对于任何foo碰巧与兼容x -> Const x x的类型,它与x -> f x时所需的类型匹配f = Const x。我知道,这有点令人难以置信,但请多多包涵。

现在,如果if h = Const,我们的getter将尽职地调用h x,它将返回Const x,getter将fmap结束,但是由于我们的定义fmap忽略其第一个参数,因此get 的结果fmap将仍然是相同的Const x,然后getter将返回。现在,我们要做的就是拆开包装,我们完成了!

getFirst :: (x, y) -> x
getFirst pair = 
    let (Const x) = firstElementGetter Const pair
    in x
Run Code Online (Sandbox Code Playgroud)

Contravariant部分有点聪明的类型级黑客。看到的,当一个函子f不仅Functor,而且Contravariant,它具有以具有的形状Const上述类型-即,它不能“包装”其类型参数内的值。

Functor可以认为是“产生”(或“包含”)值Contravariant的事物,而可以认为是“消耗”值的事物。如果必须同时使用“包装器”类型,则实现它的唯一方法是仅假装“使用”或“产生”值,而在后台忽略它们。我知道这不是一个很明确的解释,但我无法做得更好。您会发现,只需尝试实现类似的类型。

因此Getter,给了这个怪异的约束,就像确保它唯一可以做的就是“获取”值,而不是“设置”或“转换”该值一样。

  • getter的最简单实现是just s -> a
  • 一个不太简单的实现是(a -> x) -> s -> x-连续传递样式,但等效于前一个。
  • 还有一个不太简单的实现(a -> Const x a) -> s -> Const x s-我只是将两者都a替换Const x foo为different foo,但它仍然与上一个相同。

尽管第一个(最简单的)定义可以实际获取,但最后一个定义的优点是具有与其他镜片兼容的特征,因此可以在镜片组合物中使用这种吸气剂。