Lenses is an Erlang port of the Haskell library lens.
It currently implements a focused subset of the upstream API.
type Lens s t a b = forall f. Functor f => (a -> f b) -> (s -> f t)
lens:lens(s -> a, s -> b -> t) -> Lens s t a b
Lens = lens:lens(fun({_, A}) -> A end, fun({C, _}, B) -> {C, B} end),
?assertEqual(world, getter:view(Lens, {hello, world})),
?assertEqual({hello, another_world}, setter:set(Lens, another_world, {hello, world})).
lens:'_1'(), lens:'_2'() % tuple element lenses
lens:at(Key) % map key lens (Maybe a)
lens:choosing(L, R) % combine two optics over an Either
lens:alongside(L, R) % combine two optics over a pair
lens:clone_lens(Lens)
type Traversal s t a b = forall f. Applicative f => (a -> f b) -> (s -> f t)
traversal:traverse() -> forall t. Traversable t => Traversal (t a) (t b) a b
Traversal = traversal:traverse(),
?assertEqual([2, 4], setter:over(Traversal, fun(A) -> A + 1 end, As)).
traversal:both() % traversal over both elements of a 2-tuple/list
traversal:each() % traversal over list/map/tuple elements
traversal:ix(Index) % traversal over the element at index/key
traversal:beside(L, R) % combine two traversals over a pair
traversal:elements(Pred) % traversal filtered by element index
traversal:take(N), traversal:drop(N)
traversal:clone_traversal(Traversal)
type Iso s t a b = forall p f. (Profunctor p, Functor f) => p a (f b) -> p s (f t)
iso:iso(s -> a, b -> t) -> Iso s t a b
iso:from(Iso s t a b) -> Iso b a t s
Iso = iso:iso(fun(A) -> identity:run(A) end, fun(A) -> identity:identity(A) end),
?assertEqual(3, getter:view(Iso, {identity, 3})),
?assertEqual({identity, 1}, setter:set(Iso, 1, {identity, 3})),
?assertEqual({identity, 3}, getter:view(iso:from(Iso), 3)).
iso:under(Iso, AB, S) % over through the reverse iso
iso:mapping(Iso) % lift an Iso through a Functor
type Prism s t a b = forall p f. (Choice p, Applicative f) => p a (f b) -> p s (f t)
prism:prism(b -> t, s -> Either t a) -> Prism s t a b
prism:matching(Prism s t a b, s) -> Either t a
prism:preview(Prism s t a b, s) -> Maybe a
prism:review(Prism s t a b, b) -> t
-include_lib("erlando/include/op.hrl").
Prism = prism:prism(fun(A) -> {cat, A} end, fun({cat, A}) -> {right, A}; (Other) -> {left, Other} end),
Traverse = traversal:traverse(),
Compose = Traverse /'.'/ Prism,
CatsAndDogs = [{cat, kitty}, {dog, snoopy}, {cat, coffee}],
?assertEqual([kitty, coffee], fold:to_list_of(Compose, CatsAndDogs)),
?assertEqual([{cat, {my, kitty}}, {dog, snoopy}, {cat, {my, coffee}}],
setter:over(Compose, fun(Cat) -> {my, Cat} end, CatsAndDogs)),
?assertEqual({right, kitty}, prism:matching(Prism, {cat, kitty})),
?assertEqual({just, kitty}, prism:preview(Prism, {cat, kitty})),
?assertEqual({cat, kitty}, prism:review(Prism, kitty)).
prism:just(), prism:nothing() % Maybe prisms
prism:left(), prism:right() % Either prisms
prism:head() % list head prism
prism:only(A) % equality prism
prism:below(Prism) % lift a prism through a Traversable
prism:re(Prism) % reverse the direction of a prism
prism:clone_prism(Prism)
type Getting r s a = (a -> Const r a) -> (s -> Const r s)
type Getter s a = forall r. Getting r s a
getter:view(Getting a s a, s) -> a
getter:to(F) -> Getter % build a Getter from a plain function
fold:to_list_of(Getting [a] s a, s) -> [a]
fold:folding(F) -> Fold % build a Fold from a function into a Foldable
fold:length_of/2, fold:sum_of/2, fold:product_of/2
fold:max_of/2, fold:min_of/2
fold:any_of/3, fold:all_of/3, fold:none_of/3
fold:elem_of/3, fold:not_elem_of/3, fold:find_of/3
fold:first_of/2, fold:last_of/2
fold:has/2, fold:has_not/2, fold:preview/2
type Setter s t a b = (a -> Identity b) -> (s -> Identity t)
setter:over(Setter s t a b, a -> b, s) -> t
setter:setting(F) -> Setter % build a Setter from ((a -> b) -> s -> t)
setter:mapped() % Setter over any Functor
- function (->) is an instance of Profunctor
- Const r is an instance of Functor
- Identity is an Instance of Functor
- Choice is Profunctor
- Applicative is Functor
- Lens is Getter
- Lens is Traversal
- Traversal is Setter
- Traversal is Fold
- Iso is Lens
- Iso is Prism
- Prism is Traversal
- Getter is Fold
Lenses can be composed with function composition because
(p a (f b) -> p s (f t)) -> (p x (f y) -> p a (f b)) -> (p x (f y) -> p s (f t))
-compile({parse_transform, make_lenses}).
-record(state, {hello, world}).
-make_lenses([state, #{module => state}]).generates
state:hello/0
state:world/0which represents lenses of #state.hello, #state.world
Multiple records can be declared in a single attribute:
-make_lenses([state, local_state]).-compile({parse_transform, make_prisms}).
-make_prisms([{cat, [{cat, 1}, {dog, 1}]}]).generates prisms for the tagged-tuple constructors {cat, A} and {dog, A}:
cat() :: Prism {cat, A} | {dog, A} A
dog() :: Prism {cat, A} | {dog, A} ANullary constructors use arity 0, and higher-arity constructors focus on the
payload tuple. Output to a separate module is supported via #{module => Mod}.
User-defined optics are expected to satisfy the corresponding upstream lens
laws. The test suite covers the Lens get-put, put-get and put-put laws, Setter
identity and composition, Prism review/preview round trips, Iso inversion, and
the new prism optic and container optic laws.