Morph your types over a generic, case
-able elm value
MorphIndependently (Value Value.IndexOrName -> Result Morph.Error narrow) (narrow -> Value Value.IndexAndName)
Morph
between a given narrow value
and a generic value
The way it's set up, it allows build up tags as both IndexAndName
which means you can sometimes choose which one yuo want to display for a more broad format. See eachTag
unit : MorphValue ()
()
MorphValue
Used in when morphing a variant with 0 attached values.
Morph.PartsMorphEmptiable noPartPossiblyOrNever (Value.Record Value.IndexOrName -> Result PartsError groupNarrowFurther) (groupNarrow -> Value.Record Value.IndexAndName)
possibly incomplete step from and to a Record
building:
group
|> part
|> groupFinish
What can go wrong while narrowing to a Record
group : groupNarrowAssemble -> MorphValueGroupEmptiable Possibly groupNarrow_ groupNarrowAssemble
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.groupFinish
Another 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
)
part : ( group -> partValueNarrow, String ) -> MorphValue partValueNarrow -> MorphValueGroupEmptiable noPartPossiblyOrNever_ group (partValueNarrow -> groupNarrowFurther) -> MorphValueGroupEmptiable noPartNever_ group groupNarrowFurther
Continue a group assembly MorphValue
.
For example to morph a name String field, add
|> Value.Morph.part ( .name, "name" ) String.Morph.value
Once you've assembled all parts, end the builder with groupFinish
.
groupFinish : MorphValueGroupEmptiable Basics.Never record record -> MorphValue record
Conclude the Value.Morph.group
|> Value.Morph.part
builder
variant union MorphValue
Morph.choice
|> Value.Morph.variant
Value.Morph.choiceFinish
variant : ( possibilityNarrow -> choiceNarrow, String ) -> MorphValue possibilityNarrow -> Morph.ChoiceMorphEmptiable noTryPossiblyOrNever_ choiceNarrow (Value.Tagged Value.IndexOrName) ((possibilityNarrow -> Value.Tagged Value.IndexAndName) -> choiceToBroadFurther) Morph.Error -> Morph.ChoiceMorphEmptiable noTryNever_ choiceNarrow (Value.Tagged Value.IndexOrName) choiceToBroadFurther Morph.Error
Describe 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.choiceFinish
where 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.choiceFinish
choiceFinish : Morph.ChoiceMorphEmptiable Basics.Never choiceNarrow (Value.Tagged Value.IndexOrName) (choiceNarrow -> Value.Tagged Value.IndexAndName) Morph.Error -> MorphValue choiceNarrow
Conclude a Morph.choice
|> Value.Morph.variant
builder.
eachTag : MorphIndependently (tagBeforeMap -> Result (Morph.ErrorWithDeadEnd Basics.Never) tagMapped) (tagBeforeUnmap -> tagUnmapped) -> MorphIndependently (Value tagBeforeMap -> Result (Morph.ErrorWithDeadEnd never_) (Value tagMapped)) (Value tagBeforeUnmap -> Value tagUnmapped)
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 .name
We 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.jsValueMagic
descriptive : MorphIndependently (String -> Result error_ Value.IndexOrName) (Value.IndexAndName -> String)
with readable names. Use in combination with eachTag
part
MorphValue
variant
MorphValue
Value.Morph.part
order → no changeValue.Morph.part
s → 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)
toAtom : MorphIndependently (Value.AtomOrComposed narrowAtom narrowComposed_ -> Result Morph.Error narrowAtom) (broadAtom -> Value.AtomOrComposed broadAtom broadComposed_)
Morph
to a AtomOrComposed
's atom if possible
toComposed : MorphIndependently (Value.AtomOrComposed narrowAtom_ narrowComposed -> Result Morph.Error narrowComposed) (broadComposed -> Value.AtomOrComposed broadAtom_ broadComposed)
Morph
to a AtomOrComposed
's composed if possible
bits : MorphRowIndependently (Value Value.IndexOrName) (Value Value.IndexAndName) Bit
Example chain converting to Bytes
yourTypeMorphValue
|> Morph.overRow Value.Morph.bits
|> Morph.over List.Morph.bytes