erlandsona/elm-accessors/1.0.0

NOTE: the latest version is 4.2.0

Accessors

Relations are interfaces to document the relation between two data structures. For convenience, we'll call the containing structure super, and the contained structure sub. What a Relation claims is that a super is referencing a sub in some way.

Relations are the building blocks of accessors. An accessor is a function that expects a Relation and builds a new relation with it. Accessors are composable, which means you can build a chain of relations to manipulate nested structures without handling the packing and the unpacking.

Relation

type Relation structure attribute wrap

A Relation super sub wrap is a type describing how to interact with a sub data when given a super data.

The wrap exists because some types can't ensure that get will return a sub. For instance, Maybe sub may not actually contain a sub. Therefore, get returns a wrap which, in that example, will be Maybe sub

Implementation: A relation is a banal record storing a get function and an over function.

type alias Accessor dataBefore dataAfter attrBefore attrAfter reachable =
Relation attrBefore reachable attrAfter
-> Relation dataBefore reachable dataAfter

The most general version of this type that everything else specializes

type alias Lens structure transformed attribute built =
Relation attribute built transformed
-> Relation structure built transformed

This is an approximation of Van Laarhoven encoded Lenses which enable the the callers to use regular function composition to build more complex nested updates of more complicated types.

But the original "Lens" type looked more like:

type alias Lens structure attribute =
    { get : structure -> attribute
    , set : structure -> attribute -> structure
    }

unfortunately these can't be composed without defining custom composeLens, composeIso, composePrism, style functions.

whereas with this approach we're able to make use of Elm's built in << operator to get/set/over deeply nested data.

type alias Lens_ structure attribute =
Lens structure attribute attribute attribute

Simplified version of Lens but seems to break type inference for more complicated compositions.

type alias Setable structure transformed attribute built =
Relation attribute attribute built
-> Relation structure attribute transformed

Type of a composition of accessors that set can be called with.

Action functions

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

get :
( Relation attribute built attribute
-> Relation structure reachable transformed
)
-> structure
-> transformed

The get function takes:

  • An accessor,
  • A datastructure with type super and returns the value accessed by that combinator.
get (foo << bar) myRecord
set :
Setable structure transformed attribute built
-> attribute
-> structure
-> structure

The set function takes:

  • An accessor,
  • A value of the type sub,
  • A datastructure with type super and it returns the data structure, with the accessible field changed to be the set value.
set (foo << bar) "Hi!" myRecord
( Relation attribute attribute built
-> Relation structure attribute transformed
)
-> (attribute -> attribute)
-> structure
-> structure

The over function takes:

  • An accessor,
  • A function (sub -> sub),
  • A datastructure with type super and it returns the data structure, with the accessible field changed by applying the function to the existing value.
over (foo << qux) ((+) 1) myRecord
name : Accessor a b c d e -> String

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

is :
( Relation attribute built attribute
-> Relation structure reachable (Maybe transformed)
)
-> structure
-> Bool

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

Just 1234
    |> is try
--> True


Just 1234
    |> is try
--> False


["Stuff", "things"]
    |> is (ix 2)
--> False


["Stuff", "things"]
    |> is (ix 0)
--> True

Common accessors

try :
Relation attribute built transformed
-> Relation (Maybe attribute) built (Maybe transformed)

This accessor combinator lets you access values inside Maybe.

import Accessors exposing (..)
import Lens as L


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


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


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


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


over (L.qux << try << L.bar) ((+) 1) maybeRecord
--> {foo = Just {bar = 2}, qux = Nothing}
def :
attribute
-> Relation attribute reachable wrap
-> Relation (Maybe attribute) reachable wrap

This accessor combinator lets you provide a default value for otherwise failable compositions

import Dict exposing (Dict)
import Lens as L


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


get (key "foo" << def {bar = 0}) dict
--> {bar = 2}


get (key "baz" << def {bar = 0}) dict
--> {bar = 0}


-- NOTE: The following do not compile :thinking:
--get (key "foo" << try << L.bar << def 0) dict
----> 2


--get (key "baz" << try << L.bar << def 0) dict
----> 0
or :
attribute
-> ( Relation attribute attribute attribute
-> Relation structure attribute (Maybe attribute)
)
-> Relation attribute other attribute
-> Relation structure other attribute

This accessor combinator lets you provide a default value for otherwise failable compositions

import Dict exposing (Dict)
import Lens as L


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


-- NOTE: Use `def` for this.
--get (key "foo" << or {bar = 0}) dict
----> {bar = 2}


--get (key "baz" << or {bar = 0}) dict
----> {bar = 0}


get ((key "foo" << try << L.bar) |> or 0) dict
--> 2


get ((key "baz" << try << L.bar) |> or 0) dict
--> 0
Relation attribute reachable built
(Dict comparable attribute)
reachable
(Dict comparable built)

values: This accessor lets you traverse a Dict including the index of each element

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
             }


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


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


get (L.foo << values << L.bar) dictRecord
--> [("a", 2), ("b", 3), ("c", 4)] |> Dict.fromList


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

keyed: This accessor lets you traverse a Dict including the index of each element

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 }) -> (String, { bar : Int })
multiplyIfA ( key, ({ bar } as rec) ) =
    if key == "a" then
        ( key, { bar = bar * 10 } )
    else
        (key, rec)




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


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


get (L.foo << keyed << snd << L.bar) dictRecord
--> [("a", 2), ("b", 3), ("c", 4)] |> Dict.fromList


over (L.foo << keyed << snd << L.bar) ((+) 1) dictRecord
--> {foo = [("a", {bar = 3}), ("b", {bar = 4}), ("c", {bar = 5})] |> Dict.fromList}
key :
comparable
-> Relation (Maybe attribute) reachable wrap
-> Relation (Dict comparable attribute) reachable wrap

key: NON-structure preserving accessor over Dict's

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


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


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


set (key "baz" << try << L.bar) 3 dict
--> dict
Relation attribute built transformed
-> Relation (List attribute) built (List transformed)

This accessor combinator lets you access values inside List.

import Accessors exposing (..)
import Lens as L


listRecord : {foo : List {bar : Int}}
listRecord = { foo = [ {bar = 2}
                     , {bar = 3}
                     , {bar = 4}
                     ]
             }


get (L.foo << each << L.bar) listRecord
--> [2, 3, 4]


over (L.foo << each << L.bar) ((+) 1) listRecord
--> {foo = [{bar = 3}, {bar = 4}, {bar = 5}]}
Relation ( Int, attribute ) reachable built
-> Relation (List attribute) reachable (List built)

This accessor lets you traverse a list including the index of each element

import Accessors exposing (..)
import Lens as L


listRecord : {foo : List {bar : Int}}
listRecord = { foo = [ {bar = 2}
                     , {bar = 3}
                     , {bar = 4}
                     ]
             }


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




get (L.foo << eachIdx) listRecord
--> [(0, {bar = 2}), (1, {bar = 3}), (2, {bar = 4})]


over (L.foo << eachIdx) multiplyIfGTOne listRecord
--> {foo = [{bar = 2}, {bar = 30}, {bar = 40}]}


get (L.foo << eachIdx << snd << L.bar) listRecord
--> [2, 3, 4]


over (L.foo << eachIdx << snd << L.bar) ((+) 1) listRecord
--> {foo = [{bar = 3}, {bar = 4}, {bar = 5}]}
at :
Int
-> Relation v reachable wrap
-> Relation (List v) reachable (Maybe wrap)

at: Structure Preserving accessor over List members.

import Accessors exposing (..)
import Lens as L


list : List { bar : String }
list = [{ bar = "Stuff" }, { bar =  "Things" }, { bar = "Woot" }]


get (at 1) list
--> Just { bar = "Things" }


get (at 9000) list
--> Nothing


get (at 0 << L.bar) list
--> Just "Stuff"


set (at 0 << L.bar) "Whatever" list
--> [{ bar = "Whatever" }, { bar =  "Things" }, { bar = "Woot" }]


set (at 9000 << L.bar) "Whatever" list
--> list
Relation attribute built transformed
-> Relation (Array attribute) built (Array transformed)

This accessor combinator lets you access values inside Array.

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


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


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


over (L.foo << every << L.bar) ((+) 1) arrayRecord
--> {foo = Array.fromList [{bar = 3}, {bar = 4}, {bar = 5}]}
Relation ( Int, attribute ) reachable built
-> Relation (Array attribute) reachable (Array built)

This accessor lets you traverse a list including the index of each element

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 }) -> (Int, { bar : Int })
multiplyIfGTOne ( idx, ({ bar } as rec) ) =
    if idx > 0 then
        ( idx, { bar = bar * 10 } )
    else
        (idx, rec)




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


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


get (L.foo << everyIdx << snd << L.bar) arrayRecord
--> [2, 3, 4] |> Array.fromList


over (L.foo << everyIdx << snd << L.bar) ((+) 1) arrayRecord
--> {foo = [{bar = 3}, {bar = 4}, {bar = 5}] |> Array.fromList}
ix :
Int
-> Relation v reachable wrap
-> Relation (Array v) reachable (Maybe wrap)

This accessor combinator lets you access Dict members.

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

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


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


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


get (ix 9000) arr
--> Nothing


get (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 : Relation sub reachable wrap -> Relation ( sub, x ) reachable wrap

Lens over the first component of a Tuple

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)


over fst (\s -> String.toUpper s ++ "!!!") charging
--> ("IT'S OVER!!!", 1)
snd : Relation sub reachable wrap -> Relation ( x, sub ) reachable wrap
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
    |> over fst (\s -> String.toUpper s ++ "!!!")
    |> over snd ((*) 8)
--> ("IT'S OVER!!!", 9000)

Build your own accessors

Accessors are built using these functions:

(structure -> attribute)
-> ((attribute -> attribute) -> structure -> structure)
-> Relation attribute reachable wrap
-> Relation structure reachable wrap

This function lets you build an accessor for containers that have a 1:1 relation with what they contain, such as a record and one of its fields:

foo : Relation field sub wrap -> Relation { rec | foo : field } sub wrap
foo =
    makeOneToOne
        .foo
        (\change rec -> { rec | foo = change rec.foo })
((attribute -> built) -> structure -> transformed)
-> ((attribute -> attribute) -> structure -> structure)
-> Relation attribute reachable built
-> Relation structure reachable transformed

This function lets you build an accessor for containers that have a 1:N relation with what they contain, such as List (0-N cardinality) or Maybe (0-1). E.g.:

each : Relation elem sub wrap -> Relation (List elem) sub (List wrap)
each =
    makeOneToN
        List.map
        List.map

n.b. implementing those is usually considerably simpler than the type suggests.

String
-> (structure -> attribute)
-> ((attribute -> attribute) -> structure -> structure)
-> Relation attribute reachable wrap
-> Relation structure reachable wrap

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 : Relation field sub wrap -> Relation { rec | foo : field } sub wrap
foo =
    makeOneToOne_
        ".foo"
        .foo
        (\change rec -> { rec | foo = change rec.foo })
String
-> ((attribute -> built) -> structure -> transformed)
-> ((attribute -> attribute) -> structure -> structure)
-> Relation attribute reachable built
-> Relation structure reachable transformed

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 : Relation elem sub wrap -> Relation (List elem) sub (List wrap)
each =
    makeOneToN_ "[]"
        List.map
        List.map