* Focus
[[https://circleci.com/gh/tpoulsen/focus][https://circleci.com/gh/tpoulsen/focus.svg?style=svg]]
[[https://img.shields.io/hexpm/v/focus.svg]]
#+ATTR_HTML: :style margin-left: auto; margin-right: auto;
[[img/focus_lens_prism.png]]
Lightweight, pure Elixir functional optics[fn:1].
#+BEGIN_QUOTE
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.
-- [[https://docs.racket-lang.org/lens/lens-intro.html][Racket 'lens' documentation]]
#+END_QUOTE
** Usage
To construct a lens:
#+BEGIN_SRC elixir
# 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)
#+END_SRC
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:
#+BEGIN_SRC elixir
# 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"}
#+END_SRC
Their real utility comes in operating on nested data. Lenses can be created by composing other lenses in order to traverse a data structure:
#+BEGIN_SRC elixir
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: "???"
# }
# }
#+END_SRC
** Macros
*** Optic creation
+ =deflenses= :: A wrapper around =defstruct= that additionally defines lenses for the struct's keys inside the module.
#+BEGIN_SRC elixir
defmodule User do
import Lens
deflenses name: nil, age: nil
# deflenses defines %User{}, User.name_lens/0, and User.age_lens/0
end
#+END_SRC
** 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
1. Add =focus= to your list of dependencies in =mix.exs=:
#+BEGIN_SRC elixir
def deps do
[{:focus, "~> 0.3.5"}]
end
#+END_SRC
** References
+ [[https://www.schoolofhaskell.com/user/tel/a-little-lens-starter-tutorial][A Little Lens Starter Tutorial]]
+ [[https://github.com/ekmett/lens/wiki/FAQ#lens-resources][ekmett/lens package FAQ]]
+ [[https://hackage.haskell.org/package/lens-tutorial-1.0.2/docs/Control-Lens-Tutorial.html][Control.Lens Tutorial]]
* 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.