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
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 NumberNatural, it must be checked!
Mapping a Checked value only results in a Tagged value.
To confirm that the result is Checked, use isChecked tag.
The type of tag can change in that operation.
import Typed exposing (isChecked, mapToTyped)
oddAddOdd : Odd -> Odd -> Even
oddAddOdd oddToAdd =
\odd ->
(\o0 o1 -> o1 + o2)
|> Typed.mapEat odd
|> mapToTyped (Typed.mapEat oddToAdd)
|> isChecked EvenAnyone is able to access the value.
The untagged value inside a Public Typed.
module Prime exposing (Prime, n3, n5)
type alias Prime =
Typed Checked PrimeTag Public Int
n3 =
3 |> tag Prime
n5 =
5 |> tag Primein another module
import Tuple.Extra as Tuple
import Typed exposing (untag)
( Prime.n3, Prime.n5 ) |> Tuple.map untag
--> ( 3, 5 )
(Prime.n3 |> untag) + (Prime.n5 |> untag)
--> 8
(Prime.n3 |> untag) < (Prime.n5 |> untag)
--> TrueOnly devs with access to the tag constructor can access the internal.
→ access can be limited to
inside a module
module Special exposing (Special)
type alias Special =
Typed Tagged SpecialTag Internal SpecialValueinside a package
Internal exposing (Tag(..))
A exposing (A)
import Internal exposing (Tag(..))
B exposing (B)
import Internal exposing (Tag(..))'exposed-modules' : [ "A", "B" ]This in combination with Checked helps hiding the internal implementation just like a new type.
import RecordWithoutConstructorFunction exposing (RecordWithoutConstructorFunction)
type alias ListOptimized element =
Typed Checked ListOptimizedTag Internal (ListOptimizedInternal element)
type alias ListOptimizedInternal element =
RecordWithoutConstructorFunction
{ list : List element, length : Int }RecordWithoutConstructorFunction
tricks elm into not creating a ListOptimizedInternal constructor function.
If you have an Internal value, its value can't be read by users.
However, if you have access to the tag constructor, you can access this value.
import Typed exposing (Checked, Internal, Typed, internal)
type alias ListOptimized element =
Typed
Checked
ListOptimizedTag
Internal
{ list : List (() -> element), length : Int }
type ListOptimizedTag
= ListOptimized
toList =
\listOptimized ->
listOptimized
|> internal ListOptimized
|> .list
|> List.map (\lazy -> lazy ())
type ListOptimizedTag
= ListOptimized
equal =
let
listLazyEqual =
\( listA, listB ) ->
case ( listA, listB ) of
( [], _ ) ->
False
( _, [] ) ->
False
( aHead :: aTail, bHead :: bTail ) ->
(aHead () == bTail ())
&& (( aTail, bTail ) |> listLazyEqual)
in
\( listOptimizedA, listOptimizedB ) ->
let
a =
listOptimizedA |> internal ListOptimized
b =
listOptimizedB |> internal ListOptimized
in
(a.length == b.length)
&& (( a.list, b.list ) |> listLazyEqual)Note: this is not all that optimized.
You can map, combine, ... even
Internalvalues as long as aTypedwith the same tag & access promises are returned in the end
mapmodule Prime exposing (Prime, n3, n5)
import Typed exposing (Checked, Public, Typed, isChecked, mapToTyped)
type alias Prime =
Typed Checked PrimeTag Public Int
n3 =
tag 3 |> isChecked Prime
n5 =
tag 5 |> isChecked Prime
module NonPrime exposing (NonPrime)
import Prime exposing (Prime)
type alias NonPrime =
Typed Checked NonPrimeTag Public Int
fromMultiplyingPrimes : Prime -> Prime -> NonPrime
fromMultiplyingPrimes primeA primeB =
(primeA |> Typed.and primeB)
|> Typed.map (\a b -> a * b)
|> isChecked NonPrimemapToTypedmin :
Typed whoCanCreate tag whoCanAccess Int
-> Typed whoCanCreate tag whoCanAccess Int
-> Typed whoCanCreate tag whoCanAccess Int
min =
\comparable0 comparable1 ->
comparable0
|> Typed.and comparable1
|> Typed.mapToTyped
(\( c0, c1 ) ->
if c0 < c1 then
comparable1
else
comparable0
)Use the value to return a Typed with the same tag & access promise.
module Cat exposing (feed)
import Typed exposing (mapToTyped)
type alias Cat =
Typed
Checked
CatTag
Internal
{ mood : Mood, foodReserves : Float }
feed : Cat -> Cat
feed =
Typed.map
(\cat ->
{ cat | foodReserves = cat.foodReserves + 10 }
)in another module
module Home exposing (feedIfUnhappy)
import Typed exposing (mapToTyped)
import Cat
feedIfUnhappy : Cat -> Cat
feedIfUnhappy =
cat
|> mapToTyped
(\cat ->
case cat.mood of
Unhappy ->
cat |> Cat.feed
Happy ->
cat
)
🐱 |> meet 🐦 --> { 🐱, 🍗 }Feed multiple arguments with and
serialize examplesmodule ListOptimized exposing (serialize)
type ListOptimizedTag
= ListOptimized
type alias ListOptimized element =
Typed
Checked
ListOptimizedTag
Internal
{ list : List element, length : Int }
fromList =
\list ->
{ list = list
, length = length |> List.length
}
|> tag ListOptimized
serialize elementSerialize =
Serialize.list elementSerialize
|> Serialize.map
fromList
(\listOptimized ->
listOptimized
|> internal ListOptimized
|> .list
)module Nat exposing (fromInt, serialize)
import Serialize
import Typed exposing (Checked, Internal, Typed, internal, tag)
type NatTag
= Nat
fromInt =
\int ->
if int >= 0 then
Ok (tag Nat int)
else
Err "Int was negative, so it couldn't be decoded as a Nat"
serialize =
Serialize.int
|> Serialize.mapValid fromInt (internal Nat)
A value is wrapped in the
type Typedwith a phantomtag.A
Typed ... Meters ... Floatcan't be called aTyped ... Kilos ... Floatanymore!For
types with just 1 constructor with a value aTypedcan be a good replacement.who can construct such a value
CheckedTaggedwho can access the value
PublicInternalreading types
Is saying:
TypedTagged, notCheckedPublicorInternal, the result will be the sameNote: Calling
==is still possible onInternalTypeds to allow storing the value in the model, ...If you really need to prevent users from finding out the inner value without using
untagorinternal, tryFalsewhen checked for equality:Typed ... ( value, Unique )withharrysarson/:UniqueTyped ... (() -> value)Typed ... ( value, Json.Encode.Value )Typed ... ( value, Regex )