英文:
Why does Setter have Traversable constraint?
问题
当你查看lens-family-core
包中的setting
时,你会发现它的类型是Identical f => ((a -> b) -> s -> t) -> LensLike f s t a b
,而Identical
被定义为class (Traversable f, Applicative f) => Identical f
。
我理解它需要Applicative
和Identical
因为它使用了pure
和extract
,但我不确定为什么它需要Traversable
。
setting sec f = pure . sec (extract . f)```
我还发现`lens`包中的[`Setter`](https://hackage.haskell.org/package/lens-5.2.2/docs/Control-Lens-Combinators.html#t:Setter)通过[`Settable`](https://hackage.haskell.org/package/lens-5.2.2/docs/Control-Lens-Combinators.html#t:Settable)有`Traversable`约束。所以我猜想通常情况下,setter需要`Traversable`。
为什么通常情况下setter需要`Traversable`?
<details>
<summary>英文:</summary>
When you look at [`setting`](https://hackage.haskell.org/package/lens-family-core-2.1.2/docs/Lens-Family-Unchecked.html#v:setting) in `lens-family-core` package, you'll find that its type is `Identical f => ((a -> b) -> s -> t) -> LensLike f s t a b`, and `Identical` is defined as `class (Traversable f, Applicative f) => Identical f`.
I understand it needs `Applicative` and `Identical` as it uses `pure` and `extract`, but I'm not sure why it needs `Traversable`.
setting :: Identical f => ((a -> b) -> s -> t) -> LensLike f s t a b
setting sec f = pure . sec (extract . f)
I also found that [`Setter`](https://hackage.haskell.org/package/lens-5.2.2/docs/Control-Lens-Combinators.html#t:Setter) in `lens` package had `Traversable` constraint through [`Settable`](https://hackage.haskell.org/package/lens-5.2.2/docs/Control-Lens-Combinators.html#t:Settable). So I guess a setter needs `Traversable` in general.
Why does a setter need `Traversable` in general?
</details>
# 答案1
**得分**: 4
设置器实际上不需要 `Traversable`,但实际上你还是会强制使用这个约束,因为 `Identical` / `Settable` 是只允许平凡实例的类。也就是说,要么是 `Identity` 或与其同构,而这些显然也可以遍历。
注意,你可以随时编写
idenTraverse :: (Identical t, Applicative g) => (a -> g b) -> t a -> g (t b)
idenTraverse f = fmap pure . f . extract
这与标准的 `traverse` 等价。(实际上,这只需要 `Functor g`,顺便说一下。)
明确要求标准的 `Traversable` 接口可以使层次结构更清晰、更一致,实际上并不改变你对设置器能做什么或不能做什么。
<details>
<summary>英文:</summary>
Setters don't really need `Traversable`, but effectively you enforce having this constraint anyway because `Identical` / `Settable` are classes that only allows trivial instances. I.e., either `Identity` or something isomorphic to it, and all of these are obviously traversable as well.
Notice you could always write
idenTraverse :: (Identical t, Applicative g) => (a -> g b) -> t a -> g (t b)
idenTraverse f = fmap pure . f . extract
which is equivalent to the standard `traverse`. (Actually this needs only `Functor g`, incidentally.)
Explicitly requiring the standard `Traversable` interface makes for a cleaner, more consistent hierarchy, without actually changing what you can or can't do with the setters.
</details>
# 答案2
**得分**: 4
[`Settable`](https://hackage.haskell.org/package/lens-5.2.2/docs/Control-Lens-Internal-Setter.html) 和 [`Identical`](https://hackage.haskell.org/package/lens-family-core-2.1.2/docs/Lens-Family-Unchecked.html#t:Identical) 的实例必须与 `Identity` 同构。如果仅目标是确保一个函子同构于 `Identity`,则不必专门为此目的建立一个全新的类,以下约束即可:
```haskell
type Setter s t a b = forall f. (Distributive f, Lone f) => (a -> f b) -> s -> f t
这里,Lone
是一个用于精确持有一个值的函子的 Traversable
子类,不依赖于 Applicative
:
class Traversable f => Lone f where
sequenceL :: Functor m => f (m a) -> m (f a)
traverseL :: Functor m => (a -> m b) -> f a -> m (f b)
Distributive
函子 同构于函数,而 Lone
函子同构于对。如果一个函子既是 Distributive
又是 Lone
,那么它必须同构于函数函子和对函子。这只有在函数的参数类型和对的固定类型组件都是(同构于)()
的情况下才可能发生,这反过来意味着该函子必须同构于 Identity
。
以这种方式安排 Setter
的约束方式在风格上不会违背 lens 中看到的光学类型同义词的方式。实际上,这会让人想起 Getter
如何依赖于 Functor
和 Contravariant
的组合来确保它所使用的函子在其参数上是虚拟的。然而,有一个不便之处,那就是在生态系统中没有 Lone
的规范实现。由于 lens 无论如何都必须定义这个类,因此最好像 Settable
一样设置一个特殊的目的类,它有助于使签名和类型错误稍微更加自解释。
鉴于 Settable
最终是 Distributive
和 Lone
的组合代表,将它作为 Distributive
和 Traversable
的子类是有道理的。这样做可以界定类的来源,Lone
仅仅是距离 Traversable
更远的一个约束。虽然 lens-family-core 忽略了 Distributive
的约束(可能是为了避免依赖于 distributive 包),但我们可以从 对 Identical
定义的注释 中感受到类似的动机:
-- It would really be much better if comonads was in tranformers
class (Traversable f, Applicative f) => Identical f where
extract :: f a -> a
Lone
函子都是余函子,原则上,向 Identical
添加一个 Comonad
超类将允许不重新定义 extract
,同时突出显示 Identical
函子应该具有的不同方面。
最后,有几点需要总结一下。首先,您可能已经注意到,到目前为止我并没有提到 Applicative
的约束。尽管在实践中,这个约束对于使 pure
可用于 setting
的实现是必要的,但从某种意义上说,它不像其他约束那样必不可少。Distributive
函子必然是 applicative 的,而 Distributive
没有将 Applicative
作为超类,仅仅因为用 distributive 接口来表达 (<*>)
是令人讨厌的。另一方面,pure
本身可以直接根据它的术语来实现:
pureD :: Distributive g => a -> g a
pureD = cotraverse getConst . Const
其次,值得一提的是,有一种更简洁的方式来指定一个 Functor
(即 Hask 自身函子)必须同构于 Identity
:使用 Adjunction
类 来表明它必须是自己的伴随函子。然后,Setter
可能如下所示:
type Setter s t a b = forall f. Adjunction f f => (a -> f b) -> s -> f t
(顺便说一下,Distributive
函子恰好是右伴随自函子,而 Lone
函子恰好是左伴随自函子。)
然而,这个约束远非自解释,而且 Adjunction
并不是广泛使用的类,因此放弃 Settable
而选择它看起来不是一个非常吸引人的选择。
英文:
Instances of both Settable
from lens and Identical
from lens-family must be isomorphic to Identity
. (On why that must be so, see hao's answer to Isn't it redundant for Control.Lens.Setter to wrap types in functors? , and in particular the caveat at the very end of it.) Now, if our goal were merely to make sure a functor is isomorphic to Identity
, we wouldn't necessarily have to set up a brand new class just for that purpose, as the following constraint would suffice:
type Setter s t a b = forall f. (Distributive f, Lone f) => (a -> f b) -> s -> f t
Here, Lone
is a subclass of Traversable
for functors which hold exactly one value, which doesn't rely on Applicative
:
class Traversable f => Lone f where
sequenceL :: Functor m => f (m a) -> m (f a)
traverseL :: Functor m => (a -> m b) -> f a -> m (f b)
Distributive
functors are isomorphic to functions, and Lone
functors are isomorphic to pairs. If a functor is both Distributive
and Lone
, it must be isomorphic to a function functor and to a pair functor. That is only possible if the argument type of the function and the fixed-type component of the pair are both (isomorphic to) ()
, which in turn means the functor must be isomorphic to Identity
.
Arranging the constraint for Setter
in this way wouldn't have been alien to the style of optics type synonyms seen in lens. In fact, it would be reminiscent of how Getter
relies on the combination of Functor
and Contravariant
to ensure the functor it uses is phantom on its argument. One inconvenience, however, is there is no canonical implementation of Lone
in the ecosystem. Since lens would have to define the class anyway, it might as well set up a special purpose class like Settable
instead, which in any case has the advantage of making signatures and type errors slightly more self-explanatory.
Given that Settable
is ultimately a stand-in for the combination of Distributive
and Lone
, it makes sense to make it a subclass of Distributive
and Traversable
. Doing so delineates the provenance of the class, with Lone
being just one further restriction away from Traversable
. While lens-family-core omits the Distributive
constraint (presumably to avoid depending on the distributive package), we can sense a similar motivation from a comment to the definition of Identical
:
>
> -- It would really be much better if comonads was in tranformers
> class (Traversable f, Applicative f) => Identical f where
> extract :: f a -> a
>
Lone
functors are all comonads, and adding a Comonad
superclass to Identical
in principle would allow not defining extract
anew, while highlighting a different aspect of what an Identical
functor is supposed to be like.
A couple notes to wrap things up. Firstly, you may have noticed I haven't mention the Applicative
constraint at all so far. Even though the constraint is necessary in practice to make pure
available for the implementation of setting
, it is in a sense not as essential as the others. Distributive functors are necessarily applicative, and Distributive
doesn't have Applicative
as a superclass merely because (<*>)
is awkward to express in terms of the distributive interface. pure
itself, on the other hand, can be straightforwardly implemented in its terms:
pureD :: Distributive g => a -> g a
pureD = cotraverse getConst . Const
Secondly, it is worth mentioning there is a more succinct way to specify that a Functor
(that is, a Hask endofunctor) must be isomorphic to Identity
: using the Adjunction
class to state it must be adjoint to itself. Setter
, then, might be made to look like this:
type Setter s t a b = forall f. Adjunction f f => (a -> f b) -> s -> f t
(Incidentally, Distributive
functors are precisely the right adjoint endofunctors, and Lone
functors are precisely the left adjoint ones.)
This constraint, however, is far from self-explanatory, and Adjunction
isn't exactly a class in widespread use, so ditching Settable
in favour of it doesn't look like a very attractive option.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论