MackeyRMS / elm-accessors / Accessors

Accessors are a way of operating on nested data in Elm that doesn't require gobs of boilerplate.

Optic: is the opaque underlying interface that enables the rest of the library to work.


type alias Optic pr ls s t a b x y =
Base.Optic pr ls s t a b x y

Any Optic is both "lens" and "prism".

Type Aliases: are shorthands from the Optics nomenclature that make writing your own accessors more convenient and hopefully easier to understand.


type alias Iso pr ls s a x y =
Base.Iso pr ls s a x y

The isomorphism is both "lens" and "prism".


type alias Lens ls s a x y =
Base.Lens ls s a x y

Lens that cannot change type of the object.


type alias Prism pr s a x y =
Base.Prism pr s a x y

Prism that cannot change the type of the object.


type alias Traversal s a x y =
Base.Traversal s a x y

Traversal that cannot change the type of the object.

Constructors

Accessors are built using these functions:

traversal : String -> (s -> List a) -> ((a -> b) -> s -> t) -> Traversal_ s t a b x y

This exposes a description field that's necessary for use with the name function for getting unique names out of compositions of accessors. This is useful when you want type safe keys for a Dictionary but you still want to use elm/core implementation.

each : Optic pr ls a b x y -> Traversal (List a) (List b) x y
each =
    Base.traversal "[]"
        identity
        List.map

lens : String -> (s -> a) -> (s -> b -> t) -> Lens_ ls s t a b x y

This exposes a description field that's necessary for use with the name function for getting unique names out of compositions of accessors. This is useful when you want type safe keys for a Dictionary but you still want to use elm/core implementation.

foo : Optic attr view over -> Optic { rec | foo : attr } view over
foo =
    makeOneToOne
        ".foo"
        .foo
        (\change rec -> { rec | foo = change rec.foo })

prism : String -> (b -> t) -> (s -> Result t a) -> Prism_ pr s t a b x y

A prism constructor.

Parameters are: reconstructor and a splitter.

Reconstructor takes a final value and constructs a final object.

The splitter turns initial object either to final object directly (if initial object is of wrong variant), or spits out a.

iso : String -> (s -> a) -> (b -> t) -> Iso_ pr ls s t a b x y

An isomorphism constructor.

Action functions

Action functions are functions that take an accessor and let you perform a specific action on data using that accessor.

get : A_Lens_ pr s t a b -> s -> a

The get function takes:

get (foo << bar) myRecord

all : An_Optic_ pr ls s t a b -> s -> List a

Used with a Prism, think of !! boolean coercion in Javascript except type safe.

Just "Stuff"
    |> all just
--> ["Stuff"]

Nothing
    |> all just
--> []

try : An_Optic_ pr ls s t a b -> s -> Maybe a

Used with a Prism, think of !! boolean coercion in Javascript except type safe.

["Stuff", "things"]
    |> try (at 2)
--> Nothing

["Stuff", "things"]
    |> try (at 0)
--> Just "Stuff"

has : An_Optic_ pr ls s t a b -> s -> Basics.Bool

Used with a Prism, think of !! boolean coercion in Javascript except type safe.

Just 1234
    |> has just
--> True

Nothing
    |> has just
--> False

[ "Wooo", "Things" ]
    |> has (at 7)
--> False

[ "Wooo", "Things" ]
    |> has (at 0)
--> True

is : An_Optic_ pr ls s t a b -> s -> Basics.Bool

alias for has

map : An_Optic_ pr ls s t a b -> (a -> b) -> s -> t

The map function takes:

map (foo << qux) ((+) 1) myRecord

over : An_Optic_ pr ls s t a b -> (a -> b) -> s -> t

alias for map

over (foo << qux) ((+) 1) myRecord

set : An_Optic_ pr ls s t a b -> b -> s -> t

The set function takes:

set (foo << bar) "Hi!" myRecord

new : A_Prism_ ls s t a b -> b -> t

Use prism to reconstruct.

name : An_Optic_ pr ls s t a b -> String

This function gives the name of the function as a string...

to : An_Iso_ s t a b -> s -> a

Alias of get for isomorphisms

from : An_Iso_ s t a b -> b -> t

Get the inverse of an isomorphism

swap : An_Iso_ s t a b -> Optic pr ls b a t s x y

swap the parent and target types of an iso for composition chains.

Lifters for composing w/ indexed optics

ixd : An_Optic_ pr ls s t a b -> Traversal_ ( ix, s ) t a b x y

Lift an optic over an indexed traversal

Type aliases for custom and action functions


type alias Iso_ pr ls s t a b x y =
Base.Iso_ pr ls s t a b x y

The isomorphism is both "lens" and "prism".


type alias Lens_ ls s t a b x y =
Base.Lens_ ls s t a b x y

The lens is "not a prism".


type alias Prism_ pr s t a b x y =
Base.Prism_ pr s t a b x y

The prism is "not a lens".


type alias Traversal_ s t a b x y =
Base.Traversal_ s t a b x y

The traversal is neither "lens" or "prism".


type alias An_Optic pr ls s a =
Base.An_Optic pr ls s a

This MUST be a Prism or Iso


type alias An_Iso s a =
Base.An_Iso s a

This MUST be a Prism or Iso


type alias A_Lens pr s a =
Base.A_Lens pr s a

This MUST be a non-type changing Lens or Iso


type alias A_Prism ls s a =
Base.A_Prism ls s a

This MUST be a non-type changing Prism or Iso


type alias An_Optic_ pr ls s t a b =
Base.An_Optic_ pr ls s t a b

Any Optic


type alias An_Iso_ s t a b =
Base.An_Iso_ s t a b

This MUST be a Prism or Iso


type alias A_Lens_ pr s t a b =
Base.A_Lens_ pr s t a b

This MUST be a Lens or Iso


type alias A_Prism_ ls s t a b =
Base.A_Prism_ ls s t a b

This MUST be a Prism or Iso

Common Optics to mitigate import noise. Not everything is re-exported.

just : Prism_ pr (Maybe a) (Maybe b) a b x y

This accessor combinator lets you access values inside Maybe.

import Accessors exposing (..)
import Lens as L

maybeRecord : { foo : Maybe { bar : Maybe {stuff : Maybe Int} }, qux : Maybe { bar : Maybe Int } }
maybeRecord = { foo = Just { bar = Just { stuff = Just 2 } }
              , qux = Nothing
              }

try (L.foo << just << L.bar << just << L.stuff) maybeRecord
--> Just (Just 2 )

try (L.qux << just << L.bar) maybeRecord
--> Nothing

map (L.foo << just << L.bar << just << L.stuff << just) ((+) 1) maybeRecord
--> {foo = Just {bar = Just { stuff = Just 3 }}, qux = Nothing}

map (L.qux << just << L.bar << just) ((+) 1) maybeRecord
--> {foo = Just {bar = Just {stuff = Just 2}}, qux = Nothing}

ok : Prism_ pr (Result ignored a) (Result ignored b) a b x y

This accessor lets you access values inside the Ok variant of a Result. alias for Result.Accessors.onOk

import Accessors exposing (..)
import Lens as L

maybeRecord : { foo : Result String { bar : Int }, qux : Result String { bar : Int } }
maybeRecord = { foo = Ok { bar = 2 }
              , qux = Err "Not an Int"
              }

try (L.foo << ok << L.bar) maybeRecord
--> Just 2

try (L.qux << ok << L.bar) maybeRecord
--> Nothing

map (L.foo << ok << L.bar) ((+) 1) maybeRecord
--> { foo = Ok { bar = 3 }, qux = Err "Not an Int" }

map (L.qux << ok << L.bar) ((+) 1) maybeRecord
--> { foo = Ok { bar = 2 }, qux = Err "Not an Int" }

err : Prism_ pr (Result a ignored) (Result b ignored) a b x y

This accessor lets you access values inside the Err variant of a Result. alias for Result.Accessors.onErr

import Accessors exposing (..)
import Lens as L

maybeRecord : { foo : Result String { bar : Int }, qux : Result String { bar : Int } }
maybeRecord = { foo = Ok { bar = 2 }
              , qux = Err "Not an Int"
              }

try (L.foo << err) maybeRecord
--> Nothing

try (L.qux << err) maybeRecord
--> Just "Not an Int"

map (L.foo << err) String.toUpper maybeRecord
--> { foo = Ok { bar = 2 }, qux = Err "Not an Int" }

map (L.qux << err) String.toUpper maybeRecord
--> { foo = Ok { bar = 2 }, qux = Err "NOT AN INT" }

values : Traversal_ (Dict key a) (Dict key b) a b x y

values: This accessor lets you traverse a Dict including the index of each element alias for Dict.Accessors.each

import Accessors exposing (..)
import Lens as L
import Dict exposing (Dict)

dictRecord : {foo : Dict String {bar : Int}}
dictRecord = { foo = [ ("a", { bar = 2 })
                     , ("b", { bar = 3 })
                     , ("c", { bar = 4 })
                     ] |> Dict.fromList
             }

all (L.foo << values) dictRecord
--> [{bar = 2}, {bar = 3}, {bar = 4}]

map (L.foo << values << L.bar) ((*) 10) dictRecord
--> {foo = [("a", {bar = 20}), ("b", {bar = 30}), ("c", {bar = 40})] |> Dict.fromList}

all (L.foo << values << L.bar) dictRecord
--> [2, 3, 4]

map (L.foo << values << L.bar) ((+) 1) dictRecord
--> {foo = [("a", {bar = 3}), ("b", {bar = 4}), ("c", {bar = 5})] |> Dict.fromList}

keyed : Traversal_ (Dict key a) (Dict key b) ( key, a ) b x y

keyed: This accessor lets you traverse a Dict including the index of each element alias for Dict.Accessors.eachIdx

import Accessors exposing (..)
import Lens as L
import Dict exposing (Dict)

dictRecord : {foo : Dict String {bar : Int}}
dictRecord = { foo = [ ("a", { bar = 2 })
                     , ("b", { bar = 3 })
                     , ("c", { bar = 4 })
                     ] |> Dict.fromList
             }

multiplyIfA : (String, { bar : Int }) -> { bar : Int }
multiplyIfA ( key, ({ bar } as rec) ) =
    if key == "a" then
        { bar = bar * 10 }
    else
        rec


all (L.foo << keyed) dictRecord
--> [("a", {bar = 2}), ("b", {bar = 3}), ("c", {bar = 4})]

map (L.foo << keyed) multiplyIfA dictRecord
--> {foo = [("a", {bar = 20}), ("b", {bar = 3}), ("c", {bar = 4})] |> Dict.fromList}

all (L.foo << keyed << ixd L.bar) dictRecord
--> [2, 3, 4]

map (L.foo << keyed << ixd L.bar) ((+) 1) dictRecord
--> {foo = [("a", {bar = 3}), ("b", {bar = 4}), ("c", {bar = 5})] |> Dict.fromList}

key : String -> Lens ls (Dict String a) (Maybe a) x y

key: NON-structure preserving accessor over Dict's alias for Dict.Accessors.at

In terms of accessors, think of Dicts as records where each field is a Maybe.

import Dict exposing (Dict)
import Accessors exposing (..)
import Lens as L

dict : Dict String {bar : Int}
dict = Dict.fromList [("foo", {bar = 2})]

get (key "foo") dict
--> Just {bar = 2}

get (key "baz") dict
--> Nothing

try (key "foo" << just << L.bar) dict
--> Just 2

set (key "foo") Nothing dict
--> Dict.remove "foo" dict

set (key "baz" << just << L.bar) 3 dict
--> dict

keyI : Basics.Int -> Lens ls (Dict Basics.Int a) (Maybe a) x y

key: NON-structure preserving accessor over Dict's alias for Dict.Accessors.id

In terms of accessors, think of Dicts as records where each field is a Maybe.

import Dict exposing (Dict)
import Accessors exposing (..)
import Lens as L

dict : Dict Int {bar : Int}
dict = Dict.fromList [(1, {bar = 2})]

get (keyI 1) dict
--> Just {bar = 2}

get (keyI 0) dict
--> Nothing

try (keyI 1 << just << L.bar) dict
--> Just 2

set (keyI 1) Nothing dict
--> Dict.remove 1 dict

set (keyI 0 << just << L.bar) 3 dict
--> dict

key_ : (comparable -> String) -> comparable -> Lens ls (Dict comparable a) (Maybe a) x y

key_: NON-structure preserving accessor over Dict's alias for Dict.Accessors.at_

In terms of accessors, think of Dicts as records where each field is a Maybe.

import Dict exposing (Dict)
import Accessors exposing (..)
import Lens as L

dict : Dict Char {bar : Int}
dict = Dict.fromList [('C', {bar = 2})]

keyC : Char -> Lens ls (Dict Char { bar : Int })  (Maybe { bar : Int }) x y
keyC =
    key_ String.fromChar

get (keyC 'C') dict
--> Just {bar = 2}

get (keyC 'Z') dict
--> Nothing

try (keyC 'C' << just << L.bar) dict
--> Just 2

set (keyC 'C') Nothing dict
--> Dict.remove 'C' dict

set (keyC 'Z' << just << L.bar) 3 dict
--> dict

each : Traversal_ (List a) (List b) a b x y

This accessor combinator lets you access values inside List. alias for List.Accessors.each

eachIdx : Traversal_ (List a) (List b) ( Basics.Int, a ) b x y

This accessor lets you traverse a list including the index of each element alias for List.Accessors.eachIdx

at : Basics.Int -> Traversal (List a) a x y

at: Structure Preserving accessor over List members. alias for List.Accessors.at

every : Traversal_ (Array a) (Array b) a b x y

This accessor combinator lets you access values inside Array. alias for Array.Accessors.each

import Accessors exposing (..)
import Array exposing (Array)
import Lens as L

arrayRecord : {foo : Array {bar : Int}}
arrayRecord =
    { foo =
        Array.fromList [{ bar = 2 }, { bar = 3 }, {bar = 4}]
    }

all (L.foo << every << L.bar) arrayRecord
--> [2, 3, 4]

map (L.foo << every << L.bar) ((+) 1) arrayRecord
--> {foo = Array.fromList [{bar = 3}, {bar = 4}, {bar = 5}]}

everyIdx : Traversal_ (Array b) (Array c) ( Basics.Int, b ) c x y

This accessor lets you traverse an Array including the index of each element alias for Array.Accessors.eachIdx

import Accessors exposing (..)
import Lens as L
import Array exposing (Array)

arrayRecord : { foo : Array { bar : Int } }
arrayRecord = { foo = [ {bar = 2}
                      , {bar = 3}
                      , {bar = 4}
                      ] |> Array.fromList
              }

multiplyIfGTOne : (Int, { bar : Int }) -> { bar : Int }
multiplyIfGTOne ( idx, ({ bar } as rec) ) =
    if idx > 0 then
        { bar = bar * 10 }
    else
        rec


all (L.foo << everyIdx) arrayRecord
--> [(0, {bar = 2}), (1, {bar = 3}), (2, {bar = 4})]

map (L.foo << everyIdx) multiplyIfGTOne arrayRecord
--> {foo = [{bar = 2}, {bar = 30}, {bar = 40}] |> Array.fromList}

all (L.foo << everyIdx << ixd L.bar) arrayRecord
--> [2, 3, 4]

map (L.foo << everyIdx << ixd L.bar) ((+) 1) arrayRecord
--> {foo = [{bar = 3}, {bar = 4}, {bar = 5}] |> Array.fromList}

ix : Basics.Int -> Traversal (Array a) a x y

alias for Array.Accessors.at

import Accessors exposing (..)
import Array exposing (Array)
import Lens as L

arr : Array { bar : String }
arr = Array.fromList [{ bar = "Stuff" }, { bar =  "Things" }, { bar = "Woot" }]

try (ix 1) arr
--> Just { bar = "Things" }

try (ix 9000) arr
--> Nothing

try (ix 0 << L.bar) arr
--> Just "Stuff"

set (ix 0 << L.bar) "Whatever" arr
--> Array.fromList [{ bar = "Whatever" }, { bar =  "Things" }, { bar = "Woot" }]

set (ix 9000 << L.bar) "Whatever" arr
--> arr

fst : Lens_ ls ( a, two ) ( b, two ) a b x y

Lens over the first component of a Tuple alias for Tuple.Accessors.fst

import Accessors exposing (..)

charging : (String, Int)
charging = ("It's over", 1)

get fst charging
--> "It's over"

set fst "It's over" charging
--> ("It's over", 1)

map fst (\s -> String.toUpper s ++ "!!!") charging
--> ("IT'S OVER!!!", 1)

snd : Lens_ ls ( one, a ) ( one, b ) a b x y

alias for Tuple.Accessors.snd

import Accessors exposing (..)

meh : (String, Int)
meh = ("It's over", 1)

get snd meh
--> 1

set snd 1125 meh
--> ("It's over", 1125)

meh
    |> set snd 1125
    |> map fst (\s -> String.toUpper s ++ "!!!")
    |> map snd ((*) 8)
--> ("IT'S OVER!!!", 9000)

Color Iso's :raised_hands: wooo!!! For seemless composition of colors.

hexA : Base.Prism pr String TransparentColor x y

hex: This accessor lets you convert between tesk9/palette TransparentColor && SolidColor

import Color


new (hexA << swap transparent) Color.red
-->  "#CC0000"
try (hexA << swap transparent) "#C00F" -- with alpha channel
--> Just Color.red

transparent : Iso pr ls Color TransparentColor x y

solid: This accessor lets you convert between tesk9/palette TransparentColor && SolidColor

import Color


from transparent <| to transparent Color.red
--> Color.red

solid : Iso pr ls TransparentColor SolidColor x y

solid: This accessor lets you convert between tesk9/palette TransparentColor && SolidColor

import Color


from (transparent << solid) <| to (transparent << solid) Color.red
--> Color.red

oklch : Iso pr ls Color Color.Oklch.Oklch x y

oklch: This accessor lets you convert between oklch & avh4/elm-color

import Color
import Color.Round as Round


Color.blue |> to oklch |> from oklch |> Round.rgb
--> Round.rgb Color.blue

oklab : Iso pr ls Color.Oklch.Oklch Color.Oklab.Oklab x y

oklab: This accessor lets you convert between oklch & avh4/elm-color

import Color
import Color.Round as Round


 Color.blue |> to (oklch << oklab) |> from (oklch << oklab) |> Round.rgb
--> Round.rgb Color.blue

elmui : Iso pr ls Color Element.Color x y

elmui: This accessor lets you convert between oklch & avh4/elm-color

import Color


from elmui <| to elmui Color.red
--> Color.red

hsluv : Iso pr ls Color HSLuv x y

hsluv: This accessor lets you convert between oklch & avh4/elm-color

import Color
import Color.Round as Round


Color.blue |> to hsluv |> from hsluv |> Round.rgb
--> Round.rgb Color.blue