tpoulsen / Focus
Programming Languages
Labels
Projects that are alternatives of or similar to Focus
- 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
- 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.