Morph your types over a generic, case-able elm value
() MorphValue
Basically every module here has a MorphValue,
for example
of course you can combine them to reach other structures, like
Natural.Morph.integer
|> Morph.over Integer.Morph.valuefor a Natural
possibly incomplete step from and to a Record
building:
group|> part|> groupFinishWhat can go wrong while narrowing to a Record
Start assembling multiple parts.
Continue with |> part and after you're done, |> groupFinish.
An example translated from elm/json
import Decimal exposing (Decimal)
import Decimal.Morph
import RecordWithoutConstructorFunction exposing (RecordWithoutConstructorFunction)
import String.Morph
import Value
type alias Cause =
-- from lue-bird/elm-no-record-type-alias-constructor-function
RecordWithoutConstructorFunction
{ name : String
, percent : Decimal
, per100k : Decimal
}
value : MorphValue Cause
value =
Value.Morph.group
(\name percent per100k ->
{ name = name, percent = percent, per100k = per100k }
)
|> Value.Morph.part ( .name, "name" ) String.Morph.value
|> Value.Morph.part ( .percent, "percent" ) Decimal.Morph.value
|> Value.Morph.part ( .per100k, "per100k" ) Decimal.Morph.value
|> Value.Morph.groupFinishAnother example for tuples
{-| `( ..., ... )` `MorphValue`
Just use a record with descriptive names instead!
-}
tuple2 :
( Morph part0
, Morph part1
)
-> Morph ( part0, part1 )
tuple2 ( part0Morph, part1Morph ) =
Morph.named "2-tuple"
(Value.Morph.group
(\part0 part1 -> ( part0, part1 ))
|> Value.Morph.part ( Tuple.first, "part0" ) part0Morph
|> Value.Morph.part ( Tuple.second, "part1" ) part1Morph
|> Value.Morph.groupFinish
)
{-| `( ..., ..., ... )` `MorphValue`
Just use a record with descriptive names instead!
-}
tuple3 :
( Morph part0
, Morph part1
, Morph part2
)
-> Morph ( part0, part1, part2 )
tuple3 ( part0Morph, part1Morph, part2Morph ) =
Morph.named "3-tuple"
(Value.Morph.group
(\part0 part1 part2 -> ( part0, part1, part2 ))
|> Value.Morph.part ( \( part0, _, _ ) -> part0, "part0" ) part0Morph
|> Value.Morph.part ( \( _, part1, _ ) -> part1, "part1" ) part1Morph
|> Value.Morph.part ( \( _, _, part2 ) -> part2, "part2" ) part2Morph
|> Value.Morph.groupFinish
)Continue a group assembly MorphValue.
For example to morph a name String field, add
|> Value.Morph.part ( .name, "name" ) String.Morph.valueOnce you've assembled all parts, end the builder with groupFinish.
Conclude the Value.Morph.group |> Value.Morph.part builder
variant union MorphValue
Morph.choice|> Value.Morph.variantValue.Morph.choiceFinishDescribe another variant MorphValue
When you're done, end the builder with |> Value.Morph.choiceFinish
type User
= Guest GuestData
| SignedIn SignedInData
value : MorphValue User
value =
Morph.choice
(\variantGuest variantSignedIn user ->
case user of
Guest guest ->
variantGuest guest
SignedIn signedIn ->
variantSignedIn signedIn
)
|> Value.Morph.variant ( Guest, "guest" ) guestValue
|> Value.Morph.variant ( SignedIn, "signed in" ) signedInValue
|> Value.Morph.choiceFinishwhere guestValue : MorphValue GuestData and signedInValue : MorphValue SignedInData.
If a variant has no attached thing, use Value.Morph.unit
signValue : MorphValue Sign
signValue =
Morph.choice
(\positive negative sign ->
case sign of
Positive ->
positive ()
Negative ->
negative ()
)
|> Value.Morph.variant ( \() -> Positive, "Positive" ) Value.Morph.unit
|> Value.Morph.variant ( \() -> Negative, "Negative" ) Value.Morph.unit
|> Value.Morph.choiceFinishConclude a Morph.choice |> Value.Morph.variant builder.
Morph.OneToOne the tags of a Value.
From one side, a tag will contain all the information that is available,
namely IndexAndName. Your specified morph can choose one,
maybe even formatted in some way
From the other side, a tag will come as what you chose/formatted.
Now it's your jon to recreate either an IndexOrName
The simplest such tag morph is descriptive, which is simply
Morph.oneToOne Value.Name .nameWe keep only the .name and we reconstruct any tag as a name.
A more complex example is Json.Morph.compact
Morph.oneToOne
(\tag ->
case tag |> String.uncons of
Just ( 'a', tagAfterA ) ->
case tagAfterA |> String.toInt of
Just index ->
index |> Value.Index
...
)
(\tag -> "a" ++ (tag.index |> String.fromInt))We keep the .index but prefixed with "a" to make it a field name.
When recovering, we have to drop the "a" and then extract the index again.
For all the cases where extracting fails, it's usually nice to just return them as a Value.Name
since we do actually know how to decode only based on name.
An example chain that uses eachTag to decode a project to a compact Json.Encode.Value:
Project.Morph.value
|> Morph.over (Value.Morph.eachTag Json.Morph.compact)
|> Morph.over Value.Morph.json
|> Morph.over Json.Morph.jsValueMagicwith readable names. Use in combination with eachTag
part MorphValuevariant MorphValueValue.Morph.part order → no changeValue.Morph.parts → breaking changeAnother option to morph tags is Json.Morph.compact
build on existing ones
{-| `Posix` `MorphValue`
-}
posixValue : MorphValue Posix
posixValue =
Morph.named "posix"
(Morph.oneToOne
Time.posixToMillis
Time.millisToPosix
|> Morph.over Int.Morph.integer
|> Morph.over Integer.Morph.value
)or define new atoms, composed structures, ... (↓ are used by Json for example)
Morph to a AtomOrComposed's atom if possible
Morph to a AtomOrComposed's composed if possible
Morphbetween a given narrow value and a generic valueThe way it's set up, it allows build up tags as both
IndexAndNamewhich means you can sometimes choose which one yuo want to display for a more broad format. SeeeachTag