lue-bird/elm-morph/3.0.0

NOTE: the latest version is 3.0.1

Value.Morph

type alias MorphValue narrow =
MorphIndependently
(Value IndexOrName -> Result Error narrow)
(narrow -> 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.

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.value

for a Natural

grouping

type alias MorphValueGroupEmptiable noPartPossiblyOrNever groupNarrow groupNarrowFurther =
PartsMorphEmptiable
noPartPossiblyOrNever
(Record IndexOrName -> Result PartsError groupNarrowFurther)
(groupNarrow -> Record IndexAndName)

possibly incomplete step from and to a Record

building:

= TagsMissing (Emptiable (Stacked Int) Never)
| ValueError
{ index : Int
, error : Error
}

What can go wrong while narrowing to a Record

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
        )
( 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 Never record record -> MorphValue record

choice

variant union MorphValue

( possibilityNarrow -> choiceNarrow, String )
-> MorphValue possibilityNarrow
-> ChoiceMorphEmptiable
noTryPossiblyOrNever_
choiceNarrow
(Tagged IndexOrName)
( (possibilityNarrow -> Tagged IndexAndName)
-> choiceToBroadFurther
)
Error
-> ChoiceMorphEmptiable
noTryNever_
choiceNarrow
(Tagged IndexOrName)
choiceToBroadFurther
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
ChoiceMorphEmptiable
Never
choiceNarrow
(Tagged IndexOrName)
(choiceNarrow -> Tagged IndexAndName)
Error
-> MorphValue choiceNarrow

tag

MorphIndependently
(tagBeforeMap -> Result (ErrorWithDeadEnd Never) tagMapped)
(tagBeforeUnmap -> tagUnmapped)
-> MorphIndependently
( Value tagBeforeMap
-> Result (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
MorphIndependently
(String -> Result error_ IndexOrName)
(IndexAndName -> String)

with readable names. Use in combination with eachTag

Another option to morph tags is Json.Morph.compact

spin your own

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)

MorphIndependently
( AtomOrComposed narrowAtom narrowComposed_
-> Result Error narrowAtom
)
(broadAtom -> AtomOrComposed broadAtom broadComposed_)

Morph to a AtomOrComposed's atom if possible

MorphIndependently
( AtomOrComposed narrowAtom_ narrowComposed
-> Result Error narrowComposed
)
(broadComposed -> AtomOrComposed broadAtom_ broadComposed)

Morph to a AtomOrComposed's composed if possible

supported default broad formats

MorphRowIndependently
(Value IndexOrName)
(Value IndexAndName)
Bit

MorphRow from Bits.

Example chain converting to Bytes

yourTypeMorphValue
    |> Morph.overRow Value.Morph.bits
    |> Morph.over List.Morph.bytes
json : MorphOrError (Value String) Json error_