Setter为什么需要Traversable约束?

huangapple go评论57阅读模式
英文:

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

我理解它需要ApplicativeIdentical因为它使用了pureextract,但我不确定为什么它需要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&#39;ll find that its type is `Identical f =&gt; ((a -&gt; b) -&gt; s -&gt; t) -&gt; LensLike f s t a b`, and `Identical` is defined as `class (Traversable f, Applicative f) =&gt; Identical f`.

I understand it needs `Applicative` and `Identical` as it uses `pure` and `extract`, but I&#39;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) =&gt; (a -&gt; g b) -&gt; t a -&gt; g (t b)
    idenTraverse f = fmap pure . f . extract

这与标准的 `traverse` 等价。(实际上,这只需要 `Functor g`,顺便说一下。)

明确要求标准的 `Traversable` 接口可以使层次结构更清晰、更一致,实际上并不改变你对设置器能做什么或不能做什么。

<details>
<summary>英文:</summary>

Setters don&#39;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) =&gt; (a -&gt; g b) -&gt; t a -&gt; 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&#39;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 如何依赖于 FunctorContravariant 的组合来确保它所使用的函子在其参数上是虚拟的。然而,有一个不便之处,那就是在生态系统中没有 Lone 的规范实现。由于 lens 无论如何都必须定义这个类,因此最好像 Settable 一样设置一个特殊的目的类,它有助于使签名和类型错误稍微更加自解释。

鉴于 Settable 最终是 DistributiveLone 的组合代表,将它作为 DistributiveTraversable 的子类是有道理的。这样做可以界定类的来源,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) =&gt; (a -&gt; f b) -&gt; s -&gt; 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 =&gt; Lone f where
    sequenceL :: Functor m =&gt; f (m a) -&gt; m (f a)
    traverseL :: Functor m =&gt; (a -&gt; m b) -&gt; f a -&gt; 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:

>
&gt; -- It would really be much better if comonads was in tranformers
&gt; class (Traversable f, Applicative f) =&gt; Identical f where
&gt; extract :: f a -&gt; a
&gt;

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 (&lt;*&gt;) 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 =&gt; a -&gt; 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 =&gt; (a -&gt; f b) -&gt; s -&gt; 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.

huangapple
  • 本文由 发表于 2023年6月6日 08:42:21
  • 转载请务必保留本文链接:https://go.coder-hub.com/76410770.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定