This is an alternative site for discovering Elm packages. You may be looking for the official Elm package site instead.

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 Relation super sub wrap = Relation { get : super -> wrap , over : (sub -> sub) -> (super -> super) }

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 super sub wrap = (Relation sub sub sub -> Relation super sub wrap)

An Accessor super sub wrap is an alias for a function taking a Relation and giving a Relation. It can be used to make nicer signatures for the return values of makeOneToOne and makeOneToN

module Accessors exposing
  ( Relation
  , Accessor
  , get, set, over
  , makeOneToOne, makeOneToN
  )

{-| 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. 

@docs get, set, over

# Build accessors

Accessors are built using these functions:

@docs makeOneToOne, makeOneToN

# Relation

@docs Relation
@docs Accessor
-}


{-| 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 Relation super sub wrap = 
    Relation { get : super -> wrap
             , over : (sub -> sub) -> (super -> super) }


{-| An `Accessor super sub wrap` is an alias for a function taking a `Relation`
and giving a `Relation`. It can be used to make nicer signatures for the return
values of `makeOneToOne` and `makeOneToN`
-}
type alias Accessor super sub wrap = 
    (Relation sub sub sub -> Relation super sub wrap)
  

{-| id is a neutral `Relation`. It is used to end a braid of accessors (see
the implementation for get, set and over).
-}
id : Relation a a a
id =
  Relation { get  = \a -> a
           , over = \change -> (\a -> change a)
           }


{-| The get function takes:
* An accessor,
* A datastructure with type `super`
and returns the value accessed by that combinator.
```
get (foo << bar) myRecord 
```
-}
get : (Relation sub sub sub -> Relation super sub wrap) -> super -> wrap
get accessor s = 
  let (Relation relation) = (accessor id)
  in relation.get s


{-|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
```
-}
set : (Relation sub sub sub -> Relation super sub wrap) -> sub -> super -> super
set accessor value s = 
  let (Relation relation) = (accessor id)
  in relation.over (\_ -> value) s


{-|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
```
-}
over : (Relation sub sub sub -> Relation super sub wrap)
    -> (sub -> sub)
    -> super
    -> super
over accessor change s = 
  let (Relation relation) = (accessor id) in
  relation.over change s


{-| 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 }
```
-}
makeOneToOne :  (super -> sub)
             -> ((sub -> sub) -> super -> super)
             -> Relation sub   reachable wrap
             -> Relation super reachable wrap
makeOneToOne getter mapper (Relation sub) =
  Relation { get  = \super -> sub.get (getter super)
           , over = \change super -> mapper (sub.over change) super
           }

{-| 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.
-}
makeOneToN :  ((sub -> subWrap) -> super -> superWrap)
           -> ((sub -> sub) -> super -> super)
           -> Relation sub   reachable subWrap
           -> Relation super reachable superWrap
makeOneToN getter mapper (Relation sub) =
  Relation { get  = \super -> getter sub.get super
           , over = \change super -> mapper (sub.over change) super
           }