lue-bird/elm-typed-value/3.0.0

NOTE: the latest version is 8.2.0

Typed

type Typed whoCreated tag whoCanAccess value

A value is wrapped in the type Typed with a phantom tag.

A Typed ... Meters ... Float can't be called a Typed ... Kilos ... Float anymore!

For types with just 1 constructor with a value a Typed can be a good replacement.

who can construct such a value

who can access the value

all promise additional type-safety.

reading types

map :
    (value -> mappedValue)
    -> Typed whoCanCreate tag whoCanAccess value
    -> Typed Tagged tag whoCanAccess

Is saying: map works on every Typed and returns a value that is just Tagged, but not Checked. Explaining whoCanAccess:

  • If the input is Public
  • If the input is Internal,

the result will be too.

who can create

tagged

type Tagged

Anyone is able to create one of those.

Example Typed Tagged MetersTag ... Float

→ The right choice, as every Float is a valid description of Meters

tag : value -> Typed Tagged tag whoCanAccess value

Create a new tagged value.

  • can be Checked with isChecked
  • becomes Internal/Public when annotated that way

Modifying won't change the type.

map :
(value -> mappedValue)
-> Typed whoCreated tag whoCanAccess value
-> Typed Tagged tag whoCanAccess mappedValue

Alter the value inside.

If the Typed was a Checked, it becomes a Tagged.

type alias Meters =
    Typed Tagged MetersTag Public Int


go1km : Meters -> Meters
go1km =
    Typed.map ((+) 1000)
(value -> value -> value)
-> Typed whoCanCreateA tag whoCanAccess value
-> Typed whoCanCreateB tag whoCanAccess value
-> Typed Tagged tag whoCanAccess value

Use the values of 2 Typeds to return a Tagged result.

type alias PrimeNumber =
    Typed Checked PrimeNumberTag Public Int


prime3 : PrimeNumber
prime3 =
    tag 3 |> isChecked PrimeNumber


prime5 =
    tag 5 |> isChecked PrimeNumber

In another module

type alias NonPrime =
    Typed Checked NonPrimeTag Public Int


fromMultiplyingPrimes : Prime -> Prime -> NonPrime
fromMultiplyingPrimes aPrime bPrime =
    Typed.map2 (*) aPrime bPrime
        |> isChecked NonPrime

checked

type Checked

Only someone with access to the tag constructor is able to create one of those.

In effect, this means that you can only let "validated" data be of this type.

Examaple ... Checked ... NaturalNumberTag Int

→ ✓ not every Int can be called a NaturalNumber, it must be checked!

checkedTag
-> Typed whoCreated tag whoCanAccess value
-> Typed Checked checkedTag whoCanAccessChecked value

After calling tag or modifying a checked value, you get a Tagged. To tell the type that the result value is Checked, use isChecked tag.

The type of tag might even change in that operation.

oddPlusOdd : Odd -> Odd -> Even
oddPlusOdd oddToAdd =
    Typed.map2 (+) oddToAdd
        >> isChecked Even

who can access

Public

type Public

Anyone is able to access the value.

val : Typed whoCreated tag Public value -> value

Read the value inside a Public Typed.

(aValue -> bValue -> resultValue)
-> Typed whoCreatedA aTag Public aValue
-> Typed whoCreatedB bTag Public bValue
-> resultValue

Use the values of 2 Accessibles to return a result.

type alias Prime =
    Typed Checked PrimeTag Public Int


prime3 =
    tag 3 |> isChecked Prime


prime5 =
    tag 5 |> isChecked Prime

Anywhere

val2 (+) prime3 prime5
--> 8


val2 Tuple.pair prime3 prime5
--> ( 3, 5 )


if val2 (<) onePrime otherPrime then

internal

Only the ones with access to the tag constructor can access the Typed.internal.

Meaning that access can be limited to

  • inside a module
module Special exposing (Special)

type alias Special =
    Typed Tagged SpecialTag Internal SpecialValue
  • inside a package (only with Checked)
src
  └ Special
      └ Internal.elm
          module Special.Internal exposing (SpecialTag(..))
  └ SpecialPartA.elm
      module SpecialPartA exposing (SpecialPartA)
      import Special.Internal exposing (SpecialTag(..))
  └ SpecialPartB.elm
      module SpecialPartB exposing (SpecialPartB)
      import Special.Internal exposing (SpecialTag(..))
elm.json 'exposed-modules' :
  [ "SpecialPartA", "SpecialPartB" ]

This generally helps hiding implementation details.

type alias OptimizedList a =
    Typed Checked OptimizedListTag Internal (Implementation a)


type alias Implementation a =
    { list : List a, length : Int }


toList --...
internal : tag -> Typed whoCanCreate tag Internal value -> value

If you have an Internal, its value isn't readable by users.

If you have the tag however, you can access this data hidden from users.

serialize

serialize : Codec error value -> Codec error (Typed Tagged tag Public value)

A Codec to serialize Tagged Public Typeds.

tag
-> (value -> Result error value)
-> Codec error value
-> Codec error (Typed Checked tag whoCanAccess value)

A Codec to serialize Checkeds.

We don't trust that the values we encode still have the same promises as our Checkeds.

Choose a value it can convert from & to and serialize that.

module Nat exposing (serialize)


type NatTag
    = Nat


serialize =
    Serialize.int
        |> Typed.serializeChecked Nat
            (\int ->
                if int >= 0 then
                    Ok int


                else
                    Err "Int was negative, so it couldn't be decoded as a Nat"
            )


module EfficientList exposing (serialize)


type EfficientListTag
    = EfficientList


type alias EfficientList =
    Typed Checked EfficientListTag
        Internal { list : List a, length : Int }


serialize =
    Serialize.map .list identity
        |> Serialize.list
        |> Typed.serializeChecked EfficientList
            (\list ->
                { list = list
                , length = List.length length
                }
                    |> Ok
            )