{"id":17771301,"url":"https://github.com/lue-bird/elm-morph","last_synced_at":"2025-03-15T16:30:55.451Z","repository":{"id":119180302,"uuid":"486649660","full_name":"lue-bird/elm-morph","owner":"lue-bird","description":"a parser-printer: dev-friendly, general-purpose, great errors","archived":false,"fork":false,"pushed_at":"2023-12-01T20:02:43.000Z","size":1889,"stargazers_count":9,"open_issues_count":0,"forks_count":1,"subscribers_count":2,"default_branch":"master","last_synced_at":"2023-12-01T21:23:26.971Z","etag":null,"topics":["bimap","bits","codec","elm","json","narrowing","parser","parser-printer","unparser"],"latest_commit_sha":null,"homepage":"https://dark.elm.dmy.fr/packages/lue-bird/elm-morph/latest/","language":"Elm","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/lue-bird.png","metadata":{"files":{"readme":"README.md","changelog":"changes.md","contributing":"contributing.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2022-04-28T15:27:38.000Z","updated_at":"2023-12-01T21:23:30.767Z","dependencies_parsed_at":"2023-12-01T21:23:30.311Z","dependency_job_id":"94fa923c-2f99-4f48-bfff-2aa28b06afa1","html_url":"https://github.com/lue-bird/elm-morph","commit_stats":null,"previous_names":[],"tags_count":7,"template":null,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lue-bird%2Felm-morph","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lue-bird%2Felm-morph/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lue-bird%2Felm-morph/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lue-bird%2Felm-morph/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/lue-bird","download_url":"https://codeload.github.com/lue-bird/elm-morph/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":221585747,"owners_count":16847819,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bimap","bits","codec","elm","json","narrowing","parser","parser-printer","unparser"],"created_at":"2024-10-26T21:31:25.724Z","updated_at":"2025-03-15T16:30:55.445Z","avatar_url":"https://github.com/lue-bird.png","language":"Elm","funding_links":[],"categories":[],"sub_categories":[],"readme":"## [elm morph](https://dark.elm.dmy.fr/packages/lue-bird/elm-morph/latest/)\n\na parser-printer: dev-friendly, general-purpose, great errors\n\n  - 📻 related: [\"codecs\" elm-radio episode](https://elm-radio.com/episode/codecs/)\n  - 🎧 while reading: [\"Morphable\", microtonal electronic music by Sevish](https://youtu.be/J-JZhCWsk3M?t=733)\n\nOne [\"morph\"](Morph) can convert between narrow ⇄ broad types which is surprisingly useful!\nBelow some appetizers\n\n\n## [`MorphRow`](Morph#MorphRow)\n\nKnow parsers? [`MorphRow`](Morph#MorphRow) simply always creates a printer alongside. Think\n\n  - `Email/Id/Time/Path/Url.fromString` ⇄ `Email/Id/Time/Path/Url.toString`\n  - `Midi.fromBitList` ⇄ `Midi.toBitList`\n  - parse a syntax tree from tokens ⇄ build tokens from a syntax tree\n\nBuilding both in one is simpler and more reliable.\n\nA 1:1 port of [an example from `elm/parser`](https://dark.elm.dmy.fr/packages/elm/parser/latest/Parser#lazy):\n```elm\nimport Morph exposing (MorphRow, broad, match, grab)\nimport List.Morph\nimport String.Morph\n\ntype Boolean\n    = BooleanTrue\n    | BooleanFalse\n    | BooleanOr { left : Boolean, right : Boolean }\n\nboolean : MorphRow Boolean Char\nboolean =\n    Morph.recursive \"boolean\"\n        (\\step -\u003e\n            Morph.choice\n                (\\variantTrue variantFalse variantOr booleanChoice -\u003e\n                    case booleanChoice of\n                        BooleanTrue -\u003e\n                            variantTrue ()\n                        BooleanFalse -\u003e\n                            variantFalse ()\n                        BooleanOr arguments -\u003e\n                            variantOr arguments\n                )\n                |\u003e Morph.rowTry (\\() -\u003e BooleanTrue)\n                    (String.Morph.only \"true\")\n                |\u003e Morph.rowTry (\\() -\u003e BooleanFalse)\n                    (String.Morph.only \"false\")\n                |\u003e Morph.rowTry BooleanOr (or step)\n                |\u003e Morph.choiceFinish\n        )\n\nor : MorphRow Boolean Char -\u003e MorphRow { left : Boolean, right : Boolean } Char\nor step =\n    let\n        spaces : MorphRow (List ()) Char\n        spaces =\n            Morph.named \"spaces\"\n                (Morph.whilePossible (String.Morph.only \" \"))\n    in\n    Morph.narrow\n        (\\left right -\u003e { left = left, right = right })\n        |\u003e match (String.Morph.only \"(\")\n        |\u003e match (broad [] |\u003e Morph.overRow spaces)\n        |\u003e grab .left step\n        |\u003e match (broad [ () ] |\u003e Morph.overRow spaces)\n        |\u003e match (String.Morph.only \"||\")\n        |\u003e match (broad [ () ] |\u003e Morph.overRow spaces)\n        |\u003e grab .right step\n        |\u003e match (broad [] |\u003e Morph.overRow spaces)\n        |\u003e match (String.Morph.only \")\")\n\n\"((true || false) || false)\"\n    |\u003e Morph.toNarrow\n        (boolean\n            |\u003e Morph.rowFinish\n            |\u003e Morph.over List.Morph.string\n        )\n--\u003e Ok (BooleanOr { left = BooleanOr { left = BooleanTrue, right = BooleanFalse }, right = BooleanFalse })\n```\n\nWhat's different from writing a parser?\n\n  - [`Morph.choice (\\... -\u003e case ... of ...)`](Morph#choice) matches possibilities exhaustively\n  - [`grab ... ...`](Morph#grab) also shows how to access the part\n  - [`broad ...`](Morph#broad) provides a \"default value\" for the printer\n\nMorph also doesn't have `loop` or a classic `andThen`! Instead we have [atLeast, between, exactly, optional, while possible, until next, until last, ...](Morph#sequence)\n\nThis allows the quality of errors to be different to what you're used to. Here's a section of the [example app](https://github.com/lue-bird/elm-morph/blob/master/example):\n![screenshot of a combined error and description tree view, partially expanded](https://github.com/lue-bird/elm-morph/blob/master/social/point-example-error-description-tree.png?raw=true)\n\n## [`MorphValue`](Value-Morph)\n\nEasily serialize from and to elm values independent of output format.\n\nAn example adapted from [elm guide on custom types](https://guide.elm-lang.org/types/custom_types.html):\n```elm\nimport Value.Morph exposing (MorphValue)\nimport Morph\nimport String.Morph\n-- from lue-bird/elm-no-record-type-alias-constructor-function\nimport RecordWithoutConstructorFunction exposing (RecordWithoutConstructorFunction)\n\ntype User\n    = Anonymous\n    | SignedIn SignedIn\n\ntype alias SignedIn =\n    RecordWithoutConstructorFunction\n        { name : String, status : String }\n\nvalue : MorphValue User\nvalue =\n    Morph.choice\n        (\\variantAnonymous variantSignedIn user -\u003e\n            case user of\n                Anonymous -\u003e\n                    variantAnonymous ()\n                SignedIn signedIn -\u003e\n                    variantSignedIn signedIn\n        )\n        |\u003e Value.Morph.variant ( \\() -\u003e Anonymous, \"Anonymous\" ) Value.Morph.unit\n        |\u003e Value.Morph.variant ( SignedIn, \"SignedIn\" ) signedInValue\n        |\u003e Value.Morph.choiceFinish\n\nsignedInValue : MorphValue SignedIn\nsignedInValue =\n    Value.Morph.group\n        (\\name status -\u003e\n            { name = name, status = status }\n        )\n        |\u003e Value.Morph.part ( .name, \"name\" ) String.Morph.value\n        |\u003e Value.Morph.part ( .statue, \"status\" ) String.Morph.value\n        |\u003e Value.Morph.groupFinish\n```\nsurprisingly easy and clean!\n\n## [`Morph.OneToOne`](Morph#OneToOne)\n\nThe simplest of them all: convert between any two types where nothing can fail. Think\n\n  - [`List Bit`](https://dark.elm.dmy.fr/packages/lue-bird/elm-bits/latest/Bit) ⇄ [`Bytes`](https://dark.elm.dmy.fr/packages/elm/bytes/latest/), see [`List.Morph.bytes`](List-Morph#bytes)\n  - case-able [`Value`](Value) ⇄ [`Json`](Json) – both just elm union `type`s, see [`Json.Morph.value`](Json-Morph#value)\n  - type exposed from package ⇄ package-internal type\n  - decompiled AST ⇄ generated code\n\n## [`Morph`](Morph#Morph)\n\nThe parent of `MorphRow`, `MorphValue`, `Morph.OneToOne` etc.: convert between any two types. Think\n\n  - accepting numbers only in a specific range\n  - [`Decimal`](Decimal) (just digits) ⇄ `Float` with NaN and infinity\n  - [`AToZ`](AToZ) ⇄ `Char`, see [`AToZ.Morph.char`](AToZ-Morph#char)\n\n-------\n\nConfused? Hyped? Hit @lue up on anything on slack!\n\n## thanks 🌸\n\n  - [`miniBill/elm-rope`](https://dark.elm.dmy.fr/packages/miniBill/elm-rope/latest/)\n    allows our nested printer to still be `O(n)`\n  - Many ideas in [`lambda-phi/parser`](https://dark.elm.dmy.fr/packages/lambda-phi/parser/latest/) inspired [`MorphRow`](#MorphRow)'s initial design\n  - [`zwilias/elm-bytes-parser`](https://dark.elm.dmy.fr/packages/zwilias/elm-bytes-parser/latest/Bytes-Parser)\n    showed me how to convert a list of bits from and to `Bytes` and gave me the courage to make `MorphRow ... Bit`s\n  - all the elm tools, especially [`elm-verify-examples`](https://github.com/stoeffel/elm-verify-examples) and [`elm-review-documentation`](https://dark.elm.dmy.fr/packages/jfmengels/elm-review-documentation/latest/)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flue-bird%2Felm-morph","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flue-bird%2Felm-morph","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flue-bird%2Felm-morph/lists"}