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.
Action functions are functions that take an accessor and let you perform a specific action on data using that accessor.
get : (Relation sub sub sub -> Relation super sub wrap) -> super -> wrap
The get function takes:
An accessor,
A datastructure with type super
and returns the value accessed by that combinator.
get (foo << bar) myRecord
set : (Relation sub sub sub -> Relation super sub wrap) -> sub -> super -> super
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
over : (Relation sub sub sub -> Relation super sub wrap) -> (sub -> sub) -> super -> super
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
Accessors are built using these functions:
makeOneToOne : (super -> sub) -> ((sub -> sub) -> super -> super) -> Relation sub reachable wrap -> Relation super 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 })
makeOneToN : ((sub -> subWrap) -> super -> superWrap) -> ((sub -> sub) -> super -> super) -> Relation sub reachable subWrap -> Relation super reachable superWrap
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.:
onEach : Relation elem sub wrap -> Relation (List elem) sub (List wrap)
onEach =
makeOneToN
List.map
List.map
n.b. implementing those is usually considerably simpler than the type suggests.
Internal.Relation super sub 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.