lue-bird/elm-morph - version: 3.0.1

for more information visit the package's GitHub page

Package contains the following modules:

elm morph

a parser-printer: dev-friendly, general-purpose, great errors

One "morph" can convert between narrow ⇄ broad types which is surprisingly useful! Below some appetizers

MorphRow

Know parsers? MorphRow simply always creates a printer alongside. Think

Building both in one is simpler and more reliable.

A 1:1 port of an example from elm/parser:

import Morph exposing (MorphRow, broad, match, grab)
import List.Morph
import String.Morph

type Boolean
    = BooleanTrue
    | BooleanFalse
    | BooleanOr { left : Boolean, right : Boolean }

boolean : MorphRow Boolean Char
boolean =
    Morph.recursive "boolean"
        (\step ->
            Morph.choice
                (\variantTrue variantFalse variantOr booleanChoice ->
                    case booleanChoice of
                        BooleanTrue ->
                            variantTrue ()
                        BooleanFalse ->
                            variantFalse ()
                        BooleanOr arguments ->
                            variantOr arguments
                )
                |> Morph.rowTry (\() -> BooleanTrue)
                    (String.Morph.only "true")
                |> Morph.rowTry (\() -> BooleanFalse)
                    (String.Morph.only "false")
                |> Morph.rowTry BooleanOr (or step)
                |> Morph.choiceFinish
        )

or : MorphRow Boolean Char -> MorphRow { left : Boolean, right : Boolean } Char
or step =
    let
        spaces : MorphRow (List ()) Char
        spaces =
            Morph.named "spaces"
                (Morph.whilePossible (String.Morph.only " "))
    in
    Morph.narrow
        (\left right -> { left = left, right = right })
        |> match (String.Morph.only "(")
        |> match (broad [] |> Morph.overRow spaces)
        |> grab .left step
        |> match (broad [ () ] |> Morph.overRow spaces)
        |> match (String.Morph.only "||")
        |> match (broad [ () ] |> Morph.overRow spaces)
        |> grab .right step
        |> match (broad [] |> Morph.overRow spaces)
        |> match (String.Morph.only ")")

"((true || false) || false)"
    |> Morph.toNarrow
        (boolean
            |> Morph.rowFinish
            |> Morph.over List.Morph.string
        )
--> Ok (BooleanOr { left = BooleanOr { left = BooleanTrue, right = BooleanFalse }, right = BooleanFalse })

What's different from writing a parser?

Morph also doesn't have loop or a classic andThen! Instead we have atLeast, between, exactly, optional, while possible, until next, until last, ...

This allows the quality of errors to be different to what you're used to. Here's a section of the example app: screenshot of a combined error and description tree view, partially expanded

MorphValue

Easily serialize from and to elm values independent of output format.

An example adapted from elm guide on custom types:

import Value.Morph exposing (MorphValue)
import Morph
import String.Morph
-- from lue-bird/elm-no-record-type-alias-constructor-function
import RecordWithoutConstructorFunction exposing (RecordWithoutConstructorFunction)

type User
    = Anonymous
    | SignedIn SignedIn

type alias SignedIn =
    RecordWithoutConstructorFunction
        { name : String, status : String }

value : MorphValue User
value =
    Morph.choice
        (\variantAnonymous variantSignedIn user ->
            case user of
                Anonymous ->
                    variantAnonymous ()
                SignedIn signedIn ->
                    variantSignedIn signedIn
        )
        |> Value.Morph.variant ( \() -> Anonymous, "Anonymous" ) Value.Morph.unit
        |> Value.Morph.variant ( SignedIn, "SignedIn" ) signedInValue
        |> Value.Morph.choiceFinish

signedInValue : MorphValue SignedIn
signedInValue =
    Value.Morph.group
        (\name status ->
            { name = name, status = status }
        )
        |> Value.Morph.part ( .name, "name" ) String.Morph.value
        |> Value.Morph.part ( .statue, "status" ) String.Morph.value
        |> Value.Morph.groupFinish

surprisingly easy and clean!

Morph.OneToOne

The simplest of them all: convert between any two types where nothing can fail. Think

Morph

The parent of MorphRow, MorphValue, Morph.OneToOne etc.: convert between any two types. Think


Confused? Hyped? Hit @lue up on anything on slack!

thanks 🌸