Labelled wrapper
Internal.Typed creator tag accessRight untyped
A tagged thing.
→ A Typed ... Meters ... Float
isn't accepted as Typed ... Kilos ... Float
anymore!
For a type
with just 1 variant, a Typed
can be a safe replacement.
It'll save some boilerplate.
A Typed
knows
More detailed explanations and examples → readme
map :
(untyped -> untypedMapped)
-> Typed creator_ tag accessRight untyped
-> Typed Tagged tag accessRight untypedMapped
Typed
Checked
→ Tagged
Public
or Internal
, the result will be the sameNote: Just like with opaque types,
calling ==
is always possible,
even on Internal
Typed
s
to
If you really need to prevent users from finding out the inner thing without using untag
or internal
, try
False
when checked for equality:Typed ... ( thing, Unique )
with harrysarson/
: Unique
Typed ... (() -> thing)
Typed ... ( thing, Json.Encode.Value )
Typed ... ( thing, Regex )
tag : tag -> untyped -> Typed creatorChecked_ tag accessRightPublic_ untyped
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 module
Internal.Tagged
Anyone 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
Internal.Checked
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!
toChecked : tag -> Typed creator_ tag accessRight untyped -> Typed creatorChecked_ tag accessRight untyped
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 Even
If 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 Even
mapTo
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
|> ...
Internal.Public
Anyone is able to access the untyped thing
of a Typed
... Public
by using untag
untag : Typed tag_ creator_ Public untyped -> untyped
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)
--> True
toPublic : tag -> Typed creator tag accessRight_ untyped -> Typed creator tag accessRightPublic_ untyped
Confirm that it can be considered Public
by supplying the matching tag.
The result can be annotated or used as Internal
/Public
internal tag =
toPublic tag >> untag
Internal
Only 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 SpecialValue
inside a package
Internal exposing (Tag(..))
A exposing (A)
import Internal exposing (Tag(..))
B exposing (B)
import Internal exposing (Tag(..))
json
'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
ListOptimizedTag
internal : tag -> Typed creator_ tag accessRight_ untyped -> untyped
Only 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.list
internal
can be seen as
a shortcut for toPublic
, then untag
internal tag =
Typed.toPublic tag >> Typed.untag
map : (untyped -> mappedUntyped) -> Typed creator_ tag accessRight untyped -> Typed Tagged tag accessRight mappedUntyped
Change 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 map
s 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 Odd
mapToTyped : (untyped -> Typed creatorMapped tag accessRight mappedUntyped) -> Typed creator_ tag accessRight untyped -> Typed creatorMapped tag accessRight mappedUntyped
Use 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
mapTo : mappedTag -> (untyped -> mappedUntyped) -> Typed creator_ tag_ Public untyped -> Typed mappedCreatorChecked_ mappedTag mappedAccessRightPublic_ mappedUntyped
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
replace : untypedReplacement -> Typed creator_ tag accessRight_ untyped_ -> Typed Tagged tag accessRightPublic_ untypedReplacement
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 )
}
and : Typed nextCreator_ tag accessRight nextUntyped -> Typed creator_ tag accessRight untyped -> Typed Tagged tag accessRight ( untyped, nextUntyped )
You can map, combine, ... even
Internal
Typed
s as long as aTyped
with 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 3
A pretty specialized use-case which helps using tags you don't have access to inside your wrapper tag
Examples in the version 8 announcement
mapToWrap : mappedTagWrap -> (untyped -> mappedUntyped) -> Typed creator_ tag Public untyped -> Typed mappedCreatorChecked_ ( mappedTagWrap, tag ) mappedAccessRightPublic_ mappedUntyped
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
)
elementHashing
Use wrapAnd
to map multiple Typed
s
This doesn't violate any rules because you have no way of getting to the wrapped tag
mapUnwrap : (untyped -> mappedUntyped) -> Typed creator_ ( tagWrap_, tagWrapped ) accessRight untyped -> Typed Tagged tagWrapped accessRight mappedUntyped
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.encrypt
Use mapToWrap
to wrap the unwrapped thing again with a different tag
wrapToChecked : tagWrap -> Typed creator_ ( tagWrap, tagWrapped ) accessRight untyped -> Typed creatorChecked_ ( tagWrap, tagWrapped ) accessRight untyped
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 ...
wrapToPublic : tagWrap -> Typed creator ( tagWrap, tagWrapped ) accessRight_ untyped -> Typed creator ( tagWrap, tagWrapped ) accessRightPublic_ untyped
Confirm that it can be considered Public
by supplying the matching tag.
The result can be annotated or used as Internal
/Public
internal tag =
toPublic tag >> untag
wrapInternal : tagWrap -> Typed creator_ ( tagWrap, tagWrapped_ ) accessRight_ untyped -> untyped
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 Hashing
internal
can be seen as
a shortcut for wrapToPublic
, then untag
internal tag =
Typed.wrapToPublic tag >> Typed.untag
wrapAnd : Typed nextCreator_ tagFood Public nextUntyped -> Typed creator_ tagWrap accessRight untyped -> Typed Tagged ( tagWrap, tagFood ) accessRight ( untyped, nextUntyped )
and
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
)