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.
The most general version of this type that everything else specializes
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.
Simplified version of Lens but seems to break type inference for more complicated compositions.
Type of a composition of accessors that set can be called with.
Action functions are functions that take an accessor and let you perform a specific action on data using that accessor.
The get function takes:
super
and returns the value accessed by that combinator.get (foo << bar) myRecordThe set function takes:
sub,super
and it returns the data structure, with the accessible field changed to be
the set value.set (foo << bar) "Hi!" myRecordThe over function takes:
(sub -> sub),super
and it returns the data structure, with the accessible field changed by applying
the function to the existing value.over (foo << qux) ((+) 1) myRecordThis function gives the name of the function as a string...
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)
--> TrueThis 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}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
----> 0This 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
--> 0values: 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}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: 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
--> dictThis 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}]}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: 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
--> listThis 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}]}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}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
--> arrLens 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)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)Accessors are built using these functions:
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 })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.mapn.b. implementing those is usually considerably simpler than the type suggests.
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 })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
A
Relation super sub wrapis a type describing how to interact with asubdata when given asuperdata.The
wrapexists because some types can't ensure thatgetwill return asub. For instance,Maybe submay not actually contain asub. Therefore,getreturns awrapwhich, in that example, will beMaybe subImplementation: A relation is a banal record storing a
getfunction and anoverfunction.