lue-bird/elm-typed-value/6.0.1

NOTE: the latest version is 8.2.0

Typed

type Typed whoCanCreate 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 mappedValue

Is saying:

Note: Calling == is still possible on [Internal] Typeds to allow storing the value in the model.

If you really want to prevents users from finding out the inner value without using val or internalVal functions, use a typed ... (() -> value) or something that'll crash elm when checked for equality.

who can create

tagged creation

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.

Modifying won't change the type.

map :
(value -> mappedValue)
-> Typed whoCanCreate_ 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)
(aValue -> bValue -> combinedValue)
-> Typed whoCanCreateA_ tag whoCanAccess aValue
-> Typed whoCanCreateB_ tag whoCanAccess bValue
-> Typed Tagged tag whoCanAccess combinedValue

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 creation

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.

Example ... Checked ... NaturalNumberTag Int

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

checkedTag
-> Typed whoCanCreate_ tag_ whoCanAccess value
-> Typed Checked checkedTag whoCanAccess 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 access

type Public

Anyone is able to access the value.

val : Typed tag_ whoCanCreate_ Public value -> value

Read the value inside a Public Typed.

(aValue -> bValue -> resultValue)
-> Typed aTag_ whoCanCreateA_ Public aValue
-> Typed bTag_ whoCanCreateB_ Public bValue
-> resultValue

Use the values of 2 Public Typeds 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

Note: Calling (==) on Typeds causes elm to crash. This prevents users from finding out the inner value without using val or internalVal functions.

internal access

Only the ones with access to the tag constructor can access the internalVal.

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)
    Internal exposing (Tag(..))
    A exposing (A)
        import Internal exposing (Tag(..))
    B exposing (B)
        import Internal exposing (Tag(..))
    'exposed-modules' : [ "A", "B" ]

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 --...
internalVal : tag -> Typed whoCanCreate_ tag whoCanAccess_ value -> value

If you have an Internal value, its value can't be read by users.

However, if you have the tag constructor, you can access this value.

type alias OptimizedList a =
    Typed
        Checked
        OptimizedListTag
        Internal
        { list : List a, length : Int }


type OptimizedListTag
    = OptimizedList


toList =
    internalVal OptimizedList >> .list
(aValue -> bValue -> combinedValue)
-> tag
-> Typed whoCanCreateA_ tag whoCanAccessA_ aValue
-> Typed whoCanCreateB_ tag whoCanAccessB_ bValue
-> combinedValue

Take 2 Internal values with the same tag and combine them.

type alias OptimizedList a =
    Typed
        Checked
        OptimizedListTag
        Internal
        { list : List (() -> a), length : Int }


type OptimizedListTag
    = OptimizedList


equal =
    internalVal2
        (\a b ->
            let
                equalLazyLists as bs =
                    case ( as, bs ) of
                        ( [], _ ) ->
                            False


                        ( _, [] ) ->
                            False


                        ( aHead :: aTail, bHead :: bTail ) ->
                            aHead () == bTail ()
                                && equalLazyLists aTail bTail
            in
            a.length == b.length
                && equalLazyLists a.list b.list
        )
        OptimizedList

Note: this is not all that optimized.

compare

min :
Typed whoCanCreate tag whoCanAccess comparable
-> Typed whoCanCreate tag whoCanAccess comparable
-> Typed whoCanCreate tag whoCanAccess comparable

The smaller of 2 Typed comparable Typed values.

Typed.min three four
--> three
max :
Typed whoCanCreate tag whoCanAccess comparable
-> Typed whoCanCreate tag whoCanAccess comparable
-> Typed whoCanCreate tag whoCanAccess comparable

The greater of 2 Typed comparable Typed values.

Typed.max three four
--> four

transform

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

A Codec to serialize Tagged Public Typed values.

tag
-> (value -> Result error checkedValue)
-> (checkedValue -> value)
-> Codec error value
-> Codec error (Typed Checked tag whoCanAccess_ checkedValue)

A Codec to serialize Typed values.

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

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"
            )
            identity


module EfficientList exposing (serialize)


type EfficientListTag
    = EfficientList


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


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