Focus
Lightweight, pure Elixir functional optics[fn:1].
A lens is a value that composes a getter and a setter function to produce a bidirectional view into a data structure. This definition is intentionally broad—lenses are a very general concept, and they can be applied to almost any kind of value that encapsulates data. – Racket ‘lens’ documentation
Usage
To construct a lens:
# A lens for the key :name
Lens.make_lens(:name)
# A lens for the key "name"
Lens.make_lens("name")
# A lens for the second item in a tuple:
Lens.make_lens(1)
Each lens provides both a getter and a setter for the accessor it was created for.
Lenses can be used to access and/or modify structured data:
# Extract a value from a simple map:
person = %{name: "Homer"}
nameLens = Lens.make_lens(:name)
Focus.view(nameLens, person)
# "Homer"
Focus.set(nameLens, person, "Bart")
# %{name: "Bart"}
Focus.over(nameLens, person, &String.upcase/1)
# %{name: "HOMER"}
Their real utility comes in operating on nested data. Lenses can be created by composing other lenses in order to traverse a data structure:
person = %{
name: "Homer",
address: %{
locale: %{
number: 742,
street: "Evergreen Terrace",
city: "Springfield",
},
state: "???"
}
}
# To access the street, we can compose the lenses that lead there from the top level.
# Lenses can be composed with Focus.compose/2, or the infix (~>) operator.
address = Lens.make_lens(:address)
locale = Lens.make_lens(:locale)
street = Lens.make_lens(:street)
address
~> locale
~> street
|> Focus.view(person)
# "Evergreen Terrace"
address
~> locale
~> street
|> Focus.set(person, "Fake Street")
# person = %{
# name: "Homer",
# address: %{
# locale: %{
# number: 742,
# street: "Fake Street",
# city: "Springfield",
# },
# state: "???"
# }
# }
Macros
Optic creation
deflenses
- A wrapper around
defstruct
that additionally defines lenses for the struct’s keys inside the module.defmodule User do import Lens deflenses name: nil, age: nil # deflenses defines %User{}, User.name_lens/0, and User.age_lens/0 end
Functions
Optic creation
Lens.make_lens/1
Lens.make_lenses/1
Lens.idx/1
Pre-made optics
Prism.ok/0
Prism.error/0
Optic application
Focus.view/2
Focus.over/3
Focus.set/3
Focus.view_list/2
Focus.has/2
Focus.hasnt/2
Focus.fix_view/2
Focus.fix_over/3
Focus.fix_set/3
Optic composition
Focus.compose/2, (~>)
Focus.alongside/2
Installation
- Add
focus
to your list of dependencies inmix.exs
:def deps do [{:focus, "~> 0.3.5"}] end
References
Footnotes
[fn:1] This library currently combines Lenses and Prisms with Traversals in its implementation. Until v1.0.0, the API is subject to large and frequent change.