lue-bird/elm-typed-value/2.0.0

NOTE: the latest version is 8.2.0

Val

type Val whoCreated tag whoCanAccess value

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

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

For types with just 1 constructor with a value a Val 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)
    -> Val whoCanCreate tag whoCanAccess value
    -> Val Tagged tag whoCanAccess

Is saying: map works on every Val 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 Val Tagged MetersTag ... Float

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

tag : value -> Val 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)
-> Val whoCreated tag whoCanAccess value
-> Val Tagged tag whoCanAccess mappedValue

Alter the value inside.

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

type alias Meters =
    Val Tagged MetersTag Public Int


go1km : Meters -> Meters
go1km =
    Val.map ((+) 1000)
(aValue -> bValue -> combinedValue)
-> Val whoCanCreateA aTag whoCanAccess aValue
-> Val whoCanCreateB bTag whoCanAccess bValue
-> Val Tagged combinedTag whoCanAccess combinedValue

Use the values of 2 Vals to return a result. The result becomes a Tagged.

type alias PrimeNumber =
    Val Checked PrimeNumberTag Public Int


prime3 : PrimeNumber
prime3 =
    tag 3 |> isChecked PrimeNumber


prime5 =
    tag 5 |> isChecked PrimeNumber

In another module

type alias NonPrime =
    Val Checked NonPrimeTag Public Int


fromMultiplyingPrimes : Prime -> Prime -> NonPrime
fromMultiplyingPrimes aPrime bPrime =
    Val.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
-> Val whoCreated tag whoCanAccess value
-> Val 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 =
    Val.map2 (+) oddToAdd
        >> isChecked Even

who can access

Public

type Public

Anyone is able to access the value.

val : Val whoCreated tag Public value -> value

Read the value inside a Public Val.

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

Use the values of 2 Accessibles to return a result.

type alias Prime =
    Val 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 Val.internal.

Meaning that access can be limited to

  • inside a module
module Special exposing (Special)

type alias Special =
    Val 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 =
    Val Checked OptimizedListTag Internal (Implementation a)


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


toList --...
internal : tag -> Val 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 (Val Tagged tag Public value)

A Codec to serialize Tagged Public Vals.

tag
-> (value -> Result error value)
-> Codec error value
-> Codec error (Val 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
        |> Val.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 =
    Val Checked EfficientListTag
        Internal { list : List a, length : Int }


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