bChiquet / elm-accessors / 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.

Action functions

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

Build accessors

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.

Relation


type alias Relation super sub wrap =
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.