What's the difference between a lens and a partial lens?

后端 未结 3 1427
南方客
南方客 2021-02-05 16:24

A \"lens\" and a \"partial lens\" seem rather similar in name and in concept. How do they differ? In what circumstances do I need to use one or the other?

Tagging Scala

3条回答
  •  伪装坚强ぢ
    2021-02-05 16:58

    A lens is a "functional reference" that allows you to extract and/or update a generalized "field" in a larger value. For an ordinary, non-partial lens that field is always required to be there, for any value of the containing type. This presents a problem if you want to look at something like a "field" which might not always be there. For example, in the case of "the nth element of a list" (as listed in the Scalaz documentation @ChrisMartin pasted), the list might be too short.

    Thus, a "partial lens" generalizes a lens to the case where a field may or may not always be present in a larger value.

    There are at least three things in the Haskell lens library that you could think of as "partial lenses", none of which corresponds exactly to the Scala version:

    • An ordinary Lens whose "field" is a Maybe type.
    • A Prism, as described by @J.Abrahamson.
    • A Traversal.

    They all have their uses, but the first two are too restricted to include all cases, while Traversals are "too general". Of the three, only Traversals support the "nth element of list" example.

    • For the "Lens giving a Maybe-wrapped value" version, what breaks is the lens laws: to have a proper lens, you should be able to set it to Nothing to remove the optional field, then set it back to what it was, and then get back the same value. This works fine for a Map say (and Control.Lens.At.at gives such a lens for Map-like containers), but not for a list, where deleting e.g. the 0th element cannot avoid disturbing the later ones.

    • A Prism is in a sense a generalization of a constructor (approximately case class in Scala) rather than a field. As such the "field" it gives when present should contain all the information to regenerate the whole structure (which you can do with the review function.)

    • A Traversal can do "nth element of a list" just fine, in fact there are at least two different functions ix and element that both work for this (but generalize slightly differently to other containers).

    Thanks to the typeclass magic of lens, any Prism or Lens automatically works as a Traversal, while a Lens giving a Maybe-wrapped optional field can be turned into a Traversal of a plain optional field by composing with traverse.

    However, a Traversal is in some sense too general, because it is not restricted to a single field: A Traversal can have any number of "target" fields. E.g.

    elements odd
    

    is a Traversal that will happily go through all the odd-indexed elements of a list, updating and/or extracting information from them all.

    In theory, you could define a fourth variant (the "affine traversals" @J.Abrahamson mentions) that I think might correspond more closely to Scala's version, but due to a technical reason outside the lens library itself they would not fit well with the rest of the library - you would have to explicitly convert such a "partial lens" to use some of the Traversal operations with it.

    Also, it would not buy you much over ordinary Traversals, since there's e.g. a simple operator (^?) to extract just the first element traversed.

    (As far as I can see, the technical reason is that the Pointed typeclass which would be needed to define an "affine traversal" is not a superclass of Applicative, which ordinary Traversals use.)

提交回复
热议问题