如何为特定类型实现像 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
将是 x
或 y
,取决于您要提取的元素。为清楚起见,假设您要提取第一个元素。然后 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。
给定一般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
将是 x
或 y
,取决于您要提取的元素。为清楚起见,假设您要提取第一个元素。然后 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。