Labelled wrapper
Create a new Typed from its raw thing and the tag.
Rights can be chosen by annotating or using it as
passwordSafe =
tag NowItsSafe "secret1234 th1s iS private oO"
passwordSafe |> Typed.untag
--→ "secret1234 th1s iS private oO" oh no!instead
passwordSafe : Password -- important!
passwordSafe =
tag NowItsSafe "secret1234 th1s iS private oO"
passwordSafe |> Typed.untag
-- compile-time error
type alias Password =
Typed Checked PasswordTag Internal String
type PasswordTag
= NowItsSafe
passwordSafe |> Typed.internal NowItsSafe
--> "secret1234 th1s iS private oO"
-- allowed because we could prove we're in the internal moduleAnyone is able to create a Typed Tagged ....
Example: Typed Tagged MetersTag ... Float
→ every Float can be used as a quantity of Meters
Typed Tagged ... is also the result of trying to map a Checked thing
import Typed exposing (Typed, Checked, Public, tag)
type Prime
= Prime
prime3 : Typed Checked Prime Public Int
prime3 =
tag Prime 3
prime3
|> Typed.map (\prime -> prime + 1) -- tee he
--: Typed Tagged Prime Public Int→ any function that only takes Checked Prime really only receives the good stuff
Only someone with access to the tag 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!
Confirm that it can be considered Checked by supplying the matching tag.
Annotate or use the result as Tagged/Checked
-- module Even exposing (Even, add, multiply)
import Typed exposing (Checked, Public, Typed)
type alias Even =
Typed Checked EvenTag Public Int
type EvenTag
= Even
multiply : Int -> Even -> Even
multiply factor =
\even ->
even
|> Typed.map (\int -> int * factor)
|> Typed.toChecked Even
add : Even -> Even -> Even
add toAddEven =
\even ->
even
|> Typed.and toAddEven
|> Typed.map
(\( int, toAddInt ) -> int + toAddInt)
|> Typed.toChecked EvenIf the tag should change after map, mapTo
oddAddOdd : Odd -> Odd -> Even
oddAddOdd oddToAdd =
\odd ->
odd
|> Typed.and oddToAdd
-- ↓ same as mapTo Even (\( o0, o1 ) -> o0 + 01)
|> Typed.map (\( o0, o1 ) -> o0 + o1)
|> Typed.toChecked EvenmapTo is better here since you don't have weird intermediate results like
odd
-- : Odd
|> Typed.and oddToAdd
|> Typed.map (\( o0, o1 ) -> o0 + o1)
--: Typed Tagged OddTag Public Int
|> ...The thing inside the Typed Public
-- module Prime exposing (Prime, n3, n5)
type alias Prime =
Typed Checked PrimeTag Public Int
type PrimeTag
= Prime
n3 : Prime
n3 =
3 |> tag Prime
n5 : Prime
n5 =
5 |> tag Prime
-- in another module using Prime
import Typed exposing (untag)
(n3 |> untag) < (n5 |> untag)
--> TrueOnly those with access to the tag can access the internal thing.
→ 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
hides the internals just like an opaque type
type alias ListOptimized element =
Typed
Checked
ListOptimizedTag
Internal
{ list : List element, length : Int }
type ListOptimizedTag
= -- don't expose this variant
ListOptimizedTagOnly those who can show the tag
can access the Typed Internal:
import Typed exposing (Checked, Internal, Typed)
type alias ListOptimized element =
Typed
Checked
ListOptimizedTag
Internal
-- optimizations might change in the future
{ list : List element
, length : Int
}
type ListOptimizedTag
= ListOptimized
toList =
\listOptimized ->
listOptimized
|> Typed.internal ListOptimized
|> .list
type ListOptimizedTag
= ListOptimized
equal =
\( listOptimizedA, listOptimizedB ) ->
let
a =
listOptimizedA |> Typed.internal ListOptimized
b =
listOptimizedB |> Typed.internal ListOptimized
in
if a.length /= b.length then
False
else
a.list == b.listinternal can be seen as
a shortcut for toPublic, then untag
internal tag =
Typed.toPublic tag >> Typed.untagChange the untyped thing.
The result is not Checked,
so it becomes just Tagged
import Typed exposing (Public, Tagged, Typed)
type alias Meters =
Typed Tagged MetersTag Public Int
add1km : Meters -> Meters
add1km =
Typed.map (\m -> m + 1000)To confirm that the maps result is Checked
→ toChecked
import Typed exposing (Checked, Public, Typed)
type alias Odd =
Typed Checked OddTag Public Int
type OddTag
= Odd
next : Odd -> Odd
next =
\odd ->
odd
|> Typed.map (\m -> m + 2)
|> Typed.toChecked OddUse the untyped thing to return a Typed
with the same tag & access right
-- module Cat exposing (Cat, feed)
import Typed
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
import Cat
import Typed
catOnUnhappyFeed : Cat -> Cat
catOnUnhappyFeed =
cat
|> Typed.mapToTyped
(\catState ->
case catState.mood of
Unhappy ->
cat |> Cat.feed
Happy ->
cat
)Map multiple arguments with and
map which allows specifying a tag for the mapped result.
Rights of the result can be chosen by annotating or using it as
fromMultiplyingPrimes : Prime -> Prime -> NonPrime
fromMultiplyingPrimes primeA primeB =
(primeA |> Typed.and primeB)
|> Typed.mapTo NonPrime (\a b -> a * b)If the tag stays the same, just map
and if necessary add toChecked
Swap out its untyped thing for a given replacement.
The tag will stay the same but as with map, the creator will be set to Tagged.
Prefer over map if you need to allow the result to be Public
type SecretTag
= Shhh
secret : Typed Checked SecretTag Internal String
secret =
Typed.tag Shhh "dear diary"
encrypt :
Typed Checked SecretTag Internal String
-> Typed Checked SecretTag Internal String
secret
|> Typed.replace "since I can't read the secret here, we can make it Public"
--: Typed Tagged SecretTag Public String
|> Typed.untag
--> "since I can't read the secret here, we can make it Public"Also useful if you want to keep a tag for a different type:
type alias Vault secretTag =
Typed Tagged secretTag Internal { encrypted : ( String, List String ) }
for
( Typed Checked tag Internal String
, List (Typed Checked tag Internal String)
)
-> (Vault tag
-> Vault tag
)
for ( secretOne, secretOthers ) =
\vault ->
secretOne
|> Typed.replace
{ encrypted =
( secretOne |> encrypt, secretOthers |> List.map encrypt )
}You can map, combine, ... even
InternalTypeds as long as aTypedwith the same tag & access rights are returned in the end
map-- module Prime exposing (Prime, n3, n5)
import Typed exposing (Checked, Public, Typed, mapToTyped, tag)
type alias Prime =
Typed Checked PrimeTag Public Int
type PrimeTag
= Prime
n3 : Prime
n3 =
tag Prime 3
n5 : Prime
n5 =
tag Prime 5
-- module NonPrime exposing (NonPrime)
-- import Prime exposing (Prime)
type alias NonPrime =
Typed Checked NonPrimeTag Public Int
type NonPrimeTag
= NonPrime
fromMultiplyingPrimes : Prime -> Prime -> NonPrime
fromMultiplyingPrimes primeA primeB =
(primeA |> Typed.and primeB)
|> Typed.mapTo NonPrime (\a b -> a * b)mapToTyped-- module Typed.Int
smaller : Typed create tag access Int -> Typed create tag access Int -> Typed create tag access Int
smaller =
\int0Typed int1Typed ->
int0Typed
|> Typed.and int1Typed
|> Typed.mapToTyped
(\( int0, int1 ) ->
if int0 <= int1 then
int0Typed
else
-- int1 < int0
int1Typed
)
-- in another module
type OddTag
= Odd
smaller (Typed.tag Odd 3) (Typed.tag Odd 5)
--> Typed.tag Odd 3A pretty specialized use-case which helps using tags you don't have access to inside your wrapper tag
Examples in the version 8 announcement
map, then put the existing tag after a given wrapper tag
type alias Hashing subject tag =
Typed Checked tag Public (subject -> Hash)
type Each
= Each
each :
Hashing element elementHashTag
-> Hashing (List element) ( Each, elementHashTag )
each elementHashing =
Typed.mapToWrap Each
(\elementToHash ->
\list ->
list |> List.map elementToHash |> Hash.sequence
)
elementHashingUse wrapAnd to map multiple Typeds
This doesn't violate any rules because you have no way of getting to the wrapped tag
Change its untyped thing and adapt the inner, wrapped tag
The result has the same access right as before but is Tagged
since we haven't verified the changed thing
-- module Secret exposing (SecretTag, secret)
type SecretTag
= Secret
secret :
Typed creator_ kind Public a
-> Typed Checked ( SecretTag, kind ) Internal a
secret =
Typed.mapToWrap Secret
encrypt :
Typed Checked ( SecretTag, kind ) Internal String
-> Typed Checked kind Internal { encrypted : String }
encrypt =
Typed.mapUnwrap ...
-- for example in another module
type DiaryTag
= Diary
diary : Typed Checked DiaryTag Internal { encrypted : String }
diary =
secret (Typed.tag Diary "dear diary")
--: Typed Checked ( SecretTag, DiaryTag ) Internal String
|> Secret.encryptUse mapToWrap to wrap the unwrapped thing again with a different tag
Allow the creator to be Checked by supplying the wrapper tag
type Reverse
= Reverse
Int.Order.increasing
|> reverse
--: Typed Checked ( Reverse, Int.Order.Increasing ) Public ...
|> Typed.map ...
--: Typed Tagged ( Reverse, Int.Order.Increasing ) Public ...
|> Typed.wrapToChecked Reverse
--: Typed Checked ( Reverse, Int.Order.Increasing ) Public ...If you have a Typed Internal,
its untyped thing can't be read by everyone.
If you have access to the tag, you can access its untyped thing
run :
Typed Checked ( Hashing, thingTag ) Internal (thing -> Hash)
-> (thing -> Hash)
run hashing =
hashing |> Typed.wrapInternal Hashinginternal can be seen as
a shortcut for wrapToPublic, then untag
internal tag =
Typed.wrapToPublic tag >> Typed.untagand which keeps the tag from the first Typed in the chain as the wrapping tag
type alias Hashing thing tag =
Typed Checked tag Public thing
type HashBy
= HashBy
hashBy :
Map mapTag (thing -> thingMapped)
-> Hashing thingHashingTag thingMapped
-> Hashing ( HashBy, ( mapTag, thingHashingTag ) ) thingMapped
hashBy map mappedHashing =
map
|> Typed.wrapAnd mappedHashing
|> Typed.mapToWrap HashBy
(\( change, mappedHash ) ->
\toHash ->
toHash |> change |> mappedHash
)
A tagged thing.
→ A
Typed ... Meters ... Floatisn't accepted asTyped ... Kilos ... Floatanymore!For a
typewith just 1 variant, aTypedcan be a safe replacement. It'll save some boilerplate.A
TypedknowsTagged|CheckedPublic|InternalMore detailed explanations and examples → readme
reading types
TypedChecked→TaggedPublicorInternal, the result will be the sameNote: Just like with opaque types, calling
==is always possible, even onInternalTypeds toIf you really need to prevent users from finding out the inner thing without using
untagorinternal, tryFalsewhen checked for equality:Typed ... ( thing, Unique )withharrysarson/:UniqueTyped ... (() -> thing)Typed ... ( thing, Json.Encode.Value )Typed ... ( thing, Regex )