Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/quasarbright/racket-optics
optics (lenses, prisms, traversals, isos) in racket
https://github.com/quasarbright/racket-optics
Last synced: 26 days ago
JSON representation
optics (lenses, prisms, traversals, isos) in racket
- Host: GitHub
- URL: https://github.com/quasarbright/racket-optics
- Owner: quasarbright
- License: mit
- Created: 2021-11-10T19:28:07.000Z (about 3 years ago)
- Default Branch: main
- Last Pushed: 2021-11-13T23:56:31.000Z (about 3 years ago)
- Last Synced: 2024-11-06T07:42:14.753Z (3 months ago)
- Language: Racket
- Size: 5.86 KB
- Stars: 0
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# racket-optics
optics (lenses, prisms, traversals, isos) in racket# Lens
A [Lens S T] represents a getter and a setter for a target T within a structure S. For example, you could make a lens out of a getter function and a setter function for a field of a struct
Lenses support two primitive operations:
- `(lens-view l s)`, which extracts the target from the structure `s`
- `(lens-set l t s)`, which sets the target within `s` to `t`# Iso
An [Iso A B] represents an isomorphism between two types A and B. Two types are considered isomorphic if you can convert between them both ways. In other words, they are just different representations of the same information. For example, if I have two representations of a rectangle, one containing the top-left position and the bottom-right position, and another containing the top-left position, the width, and the height, those two representations are isomorphic. I can convert between them without losing any information. They are mostly useful when composed with other optics to express a transformation of one structure in terms of an equivalent one that is more suited to the transformation. In our rectangle example, if I wanted to make a rectangle wider and I was working with the top-left bottom-right representation, I could compose the iso between them with the width lens of the second representation to modify the width, despite our rectangle not having a width field. This just abstracts the process of converting to a well-suited representation, making a modification, and then converting back.
Isos support two primitive operations:
- `(iso-forward i a)`, which converts from `A` to `B`
- `(iso-backward i b)`, which converts from `B` to `A`For example, you might define an Iso `string<->symbol`, which converts from string to symbol in the forward direction, and the inverse in the backward direction
# Traversal
A [Traversal S T] is similar to a lens, except where a lens has exactly one target, a traversal may contain many targets, or none at all. For example, if you have a list of posns, you might want to update all the x-coordinates at once. One way to accomplish this would be to define a traversal which targets each posn's x-coordinate. This is sort of like `map`, except it can target values deep within a structure, and/or only parts of a structure. You can also collect the targets, which you cannot do in a map
Traversals support two primitive operations:
- `(traversal-transform t s f)`, which applies f to each target
- `(in-traversal t s)`, which iterates over each targetThe second primitive operation of a traversal is a tranformation, not a setter. With a lens, you can implement a `lens-transform` operation using `lens-view` and `lens-set`. However, since there is no `traversal-get`, you cannot use the same trick. When there are multiple targets, an updater is strictly more general than a setter. An updater can be applied to each target and do different things depending on each target's value. In contrast, a would have to do the same thing to each target. Also, if there are no targets, trying to get and set makes even less sense.
It might make sense to provide a setter which expects a sequence of the same length as the result of viewing, but I don't like the idea of that. It seems flimsy
# Prisms
A [Prism S T] is similar to a lens, but has exactly 0 or 1 target. For example, if I have a little arithmetic expression language with addition, multiplication, numbers, and variables, I might make a prism for the left sub-expression of an arithmetic expression. In the number and variable cases, there'd be zero targets. In the addition and multiplication cases, there'd be one target.
This is sort of like a first-class pattern match on a valuePrisms support two primitive operations:
- `(prism-view p s)`, which returns either the target or #f if it doesn't exist. TODO something like maybe
- `(prism-transform p s f)`, which applies f to the target, if it existsThe primitive operation is a transform for a similar reason to traversals.
# Hierarchy
An Iso is a Lens
A Lens is a Prism
A Prism is a Traversal# Composition
Optics can compose. Composing two lenses allows field-of-field access. The semantics for other optics are similar. For example, if I have a list of rectangles (top-left, bottom right) and I want to talk about each width, I'd compose a list traversal, a rectangle-tlbr<->rectangle-tlwh iso, and a rectangle-width lens. This would yield a traversal which, when transformed over, would transform the width of each rectangle in the list.
# Organization
There is a generic interface for each optic type. There are struct implementations of these interfaces, where each optic struct implements its corresponding generic interface and super-interface(s).