lue-bird/elm-typed-value/1.0.0

NOTE: the latest version is 8.2.0

Typed

building blocks

type Typed tag value whoCreatedAndWhoCanAccess

A value is wrapped in a type with a phantom tag, so that a Typed A Int ... is not a Typed B Int ....

The wrappers of type Typed

all promise additional type-safety.

You will see Typed as a function argument type.

map :
    (value -> mappedValue)
    -> Typed tag value { whoCanAccess | createdBy : whoCreated }
    -> Typed mappedTag mappedValue { whoCanAccess | createdBy : Anyone }

Is saying: map works on every Typed. The result has the same one whoCreated it.

Meaning if the input was

  • Checked or Tagged, the result becomes a Tagged
  • CheckedHidden or TaggedHidden, the result becomes a TaggedHidden
type NoUser

Only the ones with access to the tag constructor.

type Anyone

Anyone who wanted.

created by anyone

tag : value -> Typed tag value { whoCanAccess | createdBy : Anyone }

Create a new tagged value.

  • can be Checked with isChecked
  • if you start modifying that value, it becomes a Tagged
  • becomes a TaggedHidden when annotated / used as an argument
type alias Tagged tag value =
tag
value
{ canAccess : Anyone
, createdBy : Anyone
}

Every possible value can be described with this tag.

  • Tagged MetersTag Float
    • ✓ Every Float can describe Meters
  • Tagged NaturalNumberTag Int
    • ⨯ Not every Int can be called a NaturalNumber
    • Use Checked instead!

Anyone can access its value.

Instances can be created & updated everywhere.

checked

type alias Checked tag value =
tag
value
{ canAccess : Anyone
, createdBy : NoUser
}

Instances that are validated from inside the module where the tag is.

Anyone can read the valid value, but once you modify it, it just becomes a Tagged.

checkedTag
-> Typed tag value { whoCanAccess | createdBy : whoCreated }
-> Typed checkedTag value { whoCanAccess | createdBy : NoUser }

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

access

value : Typed tag value { whoCreated | canAccess : Anyone } -> value

Read the value inside a Tagged or Checked.

(aValue -> bValue -> resultValue)
-> Typed aTag aValue { whoCreateda | canAccess : Anyone }
-> Typed bTag bValue { whoCreatedb | canAccess : Anyone }
-> resultValue

Use the values of 2 Accessibles to return a result.

type alias PrimeNumber =
    Checked PrimeNumberTag Int


prime3 : PrimeNumber
prime3 =
    tag 3 |> isChecked PrimeNumber


prime5 =
    tag 5 |> isChecked PrimeNumber

Anywhere

Typed.values2 (+) prime3 prime5
--> 8


Typed.values2 Tuple.pair prime3 prime5
--> ( 3, 5 )
hiddenValueIn : tag -> Typed tag value { whoCreated | canAccess : NoUser } -> value

If you have a CheckedHidden, its value isn't readable by users.

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

hidden

type alias TaggedHidden tag value =
tag
value
{ createdBy : Anyone
, canAccess : NoUser
}

Using its value isn't allowed.

type alias Password =
    TaggedHidden PasswordTag String


type PasswordTag
    = Password Never


type alias User =
    { password : Password


    --...
    }


showUsYourPassword user =
    -- compile-time error
    user.password |> Typed.value

The only thing you can still use is == on 2 TaggedHiddens of the same type.

type alias CheckedHidden tag value =
tag
value
{ createdBy : NoUser
, canAccess : NoUser
}

A Checked value, but not everything about the value is exposed. This allows

  • hiding implementation details
  • hiding data you don't want users to see

modify

map :
(value -> mappedValue)
-> Typed tag value { whoCanAccess | createdBy : whoCreated }
-> Typed
mappedTag
mappedValue
{ whoCanAccess | createdBy : Anyone }

Alter the value inside.

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

type alias Meters =
    Typed MetersTag Int Typed


type alias Millimeters =
    Typed MilliMetersTag Typed


go1km : Meters -> Meters
go1km =
    Typed.map ((+) 1000)
(aValue -> bValue -> combinedValue)
-> Typed aTag aValue { whoCanAccess | createdBy : aWhoCanCreate }
-> Typed bTag bValue { whoCanAccess | createdBy : bWhoCanCreate }
-> Typed
combinedTag
combinedValue
{ whoCanAccess | createdBy : Anyone }

Use the values of 2 Typeds to return a result.

The result becomes a CreatableAndModifiable with the same reading permission as the 2 inputs.

type alias PrimeNumber =
    Typed PrimeNumberTag Int Checked


prime3 : PrimeNumber
prime3 =
    tag 3 |> isChecked PrimeNumber


prime5 =
    tag 5 |> isChecked PrimeNumber

Anywhere

Typed.values2 (+) prime3 prime5
--> 8

In another module

type alias NonPrime =
    Checked NonPrimeTag Int


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