{"id":17771332,"url":"https://github.com/lue-bird/elm-no-record-type-alias-constructor-function","last_synced_at":"2025-06-30T21:08:52.539Z","repository":{"id":43361965,"uuid":"466078503","full_name":"lue-bird/elm-no-record-type-alias-constructor-function","owner":"lue-bird","description":"trick: no record `type alias` constructor function","archived":false,"fork":false,"pushed_at":"2022-08-07T15:20:51.000Z","size":105,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-06-17T01:49:28.490Z","etag":null,"topics":["constructor","elm","record","type-alias"],"latest_commit_sha":null,"homepage":"https://package.elm-lang.org/packages/lue-bird/elm-no-record-type-alias-constructor-function/latest/","language":"Elm","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","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}},"created_at":"2022-03-04T10:23:53.000Z","updated_at":"2023-05-02T20:30:07.000Z","dependencies_parsed_at":"2022-07-07T18:10:59.125Z","dependency_job_id":null,"html_url":"https://github.com/lue-bird/elm-no-record-type-alias-constructor-function","commit_stats":null,"previous_names":[],"tags_count":9,"template":false,"template_full_name":null,"purl":"pkg:github/lue-bird/elm-no-record-type-alias-constructor-function","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lue-bird%2Felm-no-record-type-alias-constructor-function","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lue-bird%2Felm-no-record-type-alias-constructor-function/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lue-bird%2Felm-no-record-type-alias-constructor-function/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lue-bird%2Felm-no-record-type-alias-constructor-function/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/lue-bird","download_url":"https://codeload.github.com/lue-bird/elm-no-record-type-alias-constructor-function/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lue-bird%2Felm-no-record-type-alias-constructor-function/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":260966768,"owners_count":23090093,"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":["constructor","elm","record","type-alias"],"created_at":"2024-10-26T21:31:44.929Z","updated_at":"2025-06-20T15:07:05.928Z","avatar_url":"https://github.com/lue-bird.png","language":"Elm","readme":"\u003e trick: no record `type alias` constructor function\n\n[forms example in guide.elm-lang.org](https://guide.elm-lang.org/architecture/forms.html):\n```elm\ntype alias Model =\n    { name : String\n    , password : String\n    , passwordAgain : String\n    }\n\ninit : Model\ninit =\n    Model \"\" \"\" \"\"\n```\n↑ Every directly aliased record type gets its default constructor function.\n\nYou can trick the compiler into not creating a `Model` record constructor function:\n\n```elm\nimport RecordWithoutConstructorFunction exposing (RecordWithoutConstructorFunction)\n\ntype alias Model =\n    RecordWithoutConstructorFunction\n        { name : String\n        , password : String\n        , passwordAgain : String\n        }\n\ninit : Model\ninit =\n    -- Model \"\" \"\" \"\"  \u003c- error\n    { name = \"\", password = \"\", passwordAgain = \"\" }\n```\n\nwhere\n\n```elm\ntype alias RecordWithoutConstructorFunction record =\n    record\n```\n\n\n### tips\n\n  - find \u0026 fix your current _usages_ of record `type alias` constructor functions: [`elm-review` rule `NoRecordAliasConstructor`](https://dark.elm.dmy.fr/packages/lue-bird/elm-review-record-alias-constructor/latest/NoRecordAliasConstructor)\n\n  - insert `RecordWithoutConstructorFunction`/... where necessary: [`elm-review` rule `NoRecordAliasWithConstructor`](https://dark.elm.dmy.fr/packages/lue-bird/elm-review-record-alias-constructor/latest/NoRecordAliasWithConstructor)\n\n## why\n\nFields in a record don't have a \"natural order\".\n\n```elm\n{ age = 42, name = \"Balsa\" }\n== { name = \"Balsa\", age = 42 }\n--\u003e True\n```\n\nSo it shouldn't matter whether you write\n\n```elm\ntype alias User =\n    { name : String, age : Int }\nor  { age : Int, name : String }\n```\nas well.\n\n```elm\nUser \"Balsa\" 42\n```\nhowever relies on a specific field order in the type and is more difficult to understand/read.\nThese constructors also open up the possibility for bugs to sneak in without the compiler warning you:\n\n```elm\ntype alias User =\n    { status : String\n    , name : String \n    }\n\ndecodeUser : Decoder User\ndecodeUser =\n    map2 User\n        (field \"name\" string)\n        (field \"status\" string)\n```\nDid you spot the mistake? [↑ a similar example](https://sporto.github.io/elm-patterns/advanced/pipeline-builder.html#caveat)\n\nTo avoid these kinds of bugs, just **forbid type alias constructors**:\n```elm\ntype alias User =\n    RecordWithoutConstructorFunction ...\n```\n\nProblems don't end there.\n\n### implicit magic\n\n\u003e [\"There are worse things than being explicit\" – Evan](https://twitter.com/evancz/status/928359289135046656)\n\nMost record type aliases are not intended to work with positional arguments!\n`Model` is the perfect example.\n\nEven if you think it's ok currently, no one reminds you when you add new fields.\n\n### there are better alternatives\n\nIt's so easy to create an explicit constructor\n\n```elm\nxy : Float -\u003e Float -\u003e { x : Float, y : Float }\n```\n\nAs argued, unnamed arguments shouldn't be the _default_.\n\nAdditionally, your record will be more descriptive and type-safe as a `type`\n\n```elm\ntype Cat\n    = Cat { mood : Mood, birthTime : Time.Posix }\n```\n\nto make wrapping, unwrapping and combining easier, you can try [typed-value](https://dark.elm.dmy.fr/packages/lue-bird/elm-typed-value/latest/).\n\n### only works in very limited scenarios\n\n```elm\ntype alias Record =\n    { someField : () }\n\ntype alias Indirect =\n    Record\n```\n\n  - `Record` has a constructor function\n  - `Indirect` doesn't have a constructor function\n\n```elm\ntype alias Extended record =\n    { record | someField : () }\n\ntype alias Constructed =\n    Extended {}\n```\n\n  - `Constructed`, `Extended` don't have constructor functions\n\n### name clash with variant\n\nexample adapted from [`Elm.Syntax.Exposing`](https://dark.elm.dmy.fr/packages/stil4m/elm-syntax/latest/Elm-Syntax-Exposing#TopLevelExpose)\n```elm\ntype TopLevelExpose\n    = ...\n    | TypeExpose TypeExpose\n\ntype alias TypeExpose =\n    { name : String\n    , open : Maybe Range\n    }\n```\n\u003e NAME CLASH - multiple defined `TypeExpose` type constructors.\n\u003e \n\u003e How can I know which one you want? Rename one of them!\n\nEither rename `type alias TypeExpose` to `TypeExposeData`/... or\n```elm\ntype alias TypeExpose =\n    RecordWithoutConstructorFunction ...\n```\nand **get rid of the compile-time error**\n\n### misunderstood as special magic constructors\n\n[experience report by John Pavlick: LETWW, Part 2: \"Regular expressions are quite confusing and difficult to use.\"](https://dev.to/jmpavlick/regular-expressions-are-quite-confusing-and-difficult-to-use-50l7) (read it):\n\n\u003e My prior lack of understanding was due to a mental disconnect between the two true sentences, \"record type aliases come with implicit constructors\" and \"all constructors are functions\"\n\u003e \n\u003e \\[...\\] After marinating for over a decade in a type system where type names are \"special\" and have to be invoked only in certain special-case contexts - I couldn't see \\[...\\]:\n\u003e \n\u003e Type names, just like everything else in Elm, are Not Special. They're constructors for a value [= functions].\n\n### `succeed`/`constant` are misused\n\nI'd consider `succeed`/`constant`/... with a constant value in record field value `Decoder`s/`Generator`s/... unidiomatic.\n\n```elm\nprojectDecoder : Decoder Project\nprojectDecoder =\n    map3\n        (\\name scale selected -\u003e\n            { name = name\n            , scale = scale\n            , selected = selected\n            }\n        )\n        (field \"name\" string)\n        (field \"scale\" float)\n        (succeed NothingSelected) -- weird\n```\n\nConstants should much rather be introduced explicitly in a translation step:\n\n```elm\nprojectDecoder : Decoder Project\nprojectDecoder =\n    map2\n        (\\name scale -\u003e\n            { name = name\n            , scale = scale\n            , selected = NothingSelected\n            }\n        )\n        (field \"name\" string)\n        (field \"scale\" float)\n```\n\nFor record `Codec`s (from [MartinSStewart's `elm-serialize`](https://package.elm-lang.org/packages/MartinSStewart/elm-serialize/latest/) in this example) where we don't need to encode every field value:\n\n```elm\nserializeProject : Codec String Project\nserializeProject =\n    record Project\n        |\u003e field .name string\n        |\u003e field .scale float\n        |\u003e field .selected\n            (succeed NothingSelected)\n        |\u003e finishRecord\n```\n`succeed` is a weird concept for codecs because some dummy value must be encoded which will never be read.\n\nIt does not exist in elm-serialize, but it does exist in [miniBill's `elm-codec`](https://package.elm-lang.org/packages/miniBill/elm-codec/latest) (, [prozacchiwawa's `elm-json-codec`](https://package.elm-lang.org/packages/prozacchiwawa/elm-json-codec/latest), ...):\n\u003e Create a Codec that produces null as JSON and always decodes as the same value.\n\nDo you really want this behavior? If not, you'll need\n```elm\nserializeProject : Codec String Project\nserializeProject =\n    record\n        (\\name scale -\u003e\n            { name = name\n            , scale = scale\n            , selected = NothingSelected\n            }\n        )\n        |\u003e field .name string\n        |\u003e field .scale float\n        |\u003e finishRecord\n```\nWhy not consistently use this record constructing method?\n\nThis will also be used often for versioning\n```elm\nenum ProjectVersion0 [ ... ]\n    |\u003e andThen\n        (\\version -\u003e\n            case version of\n                ProjectVersion0 -\u003e\n                    record\n                        (\\name -\u003e { name = name, scale = 1 })\n                        |\u003e field .name string\n                        |\u003e finishRecord\n                    \n                ...\n        )\n```\nAgain: Why not consistently use this record constructing method?\n\n## suggestions?\n→ [contributing](https://github.com/lue-bird/elm-no-record-type-alias-constructor-function/blob/master/contributing.md).\n\n## why a whole package\n\n`RecordWithoutConstructorFunction.elm` can simply be copied to your project.\n\n  - [the `elm-review` rule that adds your `RecordWithoutConstructorFunction`](https://dark.elm.dmy.fr/packages/lue-bird/elm-review-record-alias-constructor/latest/NoRecordAliasWithConstructor) will still work\n\nHowever, if you want\n\n  - no separate `RecordWithoutConstructorFunction`s hanging around\n  - a single place for up to date public documentation\n  - a common recognizable name\n  - safety that `RecordWithoutConstructorFunction` will never be aliased to a different type\n\nconsider\n```monospace\nelm install lue-bird/elm-no-record-type-alias-constructor-function\n```\n\n## fields constructor too verbose?\n\n```elm\ndecodeUser =\n    map2 (\\name status -\u003e { name = name, status = status })\n        (field \"name\" string)\n        (field \"status\" string)\n```\nis rather verbose.\n\nThere are languages that introduce extra sugar:\n\n### field addition\n\n```elm\nsucceed {}\n    |\u003e field \u0026name \"name\" string\n    |\u003e field \u0026status \"status\" string\n```\n\nwould be simple and neat. elm dropped this for simplicity.\n\n\n### field \"punning\"\n\nThis is present in purescript and other languages\n\n```elm\nmap2 (\\name status -\u003e { name, status })\n    (field \"name\" string)\n    (field \"status\" string)\n```\nJeroen's made a convincing [argument on negative consequences for descriptiveness](https://discourse.elm-lang.org/t/record-creation-shortcut/6136/10) in contexts of functions growing larger.\n\n\n### positional by fields\n\n```elm\nmap2 { name, status }\n    (field \"name\" string)\n    (field \"status\" string)\n```\nwith either\n```elm\n{ x, y }         : Int -\u003e Int -\u003e { x : Int, y : Int }\n{ x =, y = }     : Int -\u003e Int -\u003e { x : Int, y : Int }\n{ x = _, y = _ } : Int -\u003e Int -\u003e { x : Int, y : Int }\n{ \\x y }         : Int -\u003e Int -\u003e { x : Int, y : Int }\n```\nThe last one was proposed in a [very old discussion](https://github.com/elm/compiler/issues/73)\n\nIt's concise but quite limited in what it can do while not fixing many problems:\n\n- problems with [`succeed`/`constant` misuse](https://dark.elm.dmy.fr/packages/lue-bird/elm-no-record-type-alias-constructor-function/latest/) remain\n    - → confusing `{ x, y = 0, z }` syntax necessary\n\n- less intuitive?\n    - recognizing it as a function\n    - unlike punning in other languages\n\n- less explicit than record field punning\n  a.k.a \"doesn't scale well\" (arguments are taken as they come, can't be combined, ...)\n\n### `type alias` positional 1-field records\n\nExplored in [\"Safe and explicit records constructors exploration\"](https://discourse.elm-lang.org/t/safe-and-explicit-records-constructors-exploration/4823).\n\nInstead of\n```elm\nPoint : Int -\u003e Int -\u003e Point\n\nPoint : { x : Int } -\u003e { y : Int } -\u003e Point\n```\n\n- problems with \"doesn't scale:\n  can't expose extensible, extended indirect\" remain (if not fixed somehow)\n- defined field ordering should explicitly not matter\n  on by-nature-non-positional records\n- could again seem\n  [magical and unintuitive](https://dev.to/jmpavlick/regular-expressions-are-quite-confusing-and-difficult-to-use-50l7)\n\n\n## improve safety\n\n```elm\nCodec.group (\\{ x } { y } -\u003e { x = x, y = y })\n    |\u003e Codec.part ( .x, \\x -\u003e { x = x })\n        Codec.int\n    |\u003e Codec.part ( .y, \\y -\u003e { y = y })\n        Codec.int\n```\nor better, to always avoid shadowing errors:\n```elm\nCodec.group (\\p0 p1 -\u003e { x = .x p0, y = .y p1 })\n    |\u003e Codec.part ( .x, \\x -\u003e { x = x })\n        Codec.int\n    |\u003e Codec.part ( .y, \\y -\u003e { y = y })\n        Codec.int\n```\n\nMaking this less verbose with not-necessarily-language-sugared-but-[code-generable](https://dark.elm.dmy.fr/packages/lue-bird/elm-review-missing-record-field-lens/latest/) field stuff:\n\n```elm\nCodec.group (\\p0 p1 -\u003e { x = .x p0, y = .y p1 })\n    |\u003e Codec.part Record.x Codec.int\n    |\u003e Codec.part Record.y Codec.int\n```\n\nwith\n```elm\nimport Record exposing (x, y)\n\nx : Part x { x : x } { record_ | x : x }\nx =\n    part\n        { access = .x\n        , named = \\x -\u003e { x = x }\n        , alter = \\alter r -\u003e { r | x = r.x |\u003e alter }\n        , description = \"x\"\n        }\n```\n\nI bet ideas like this won't be widely adopted (in packages) because setting up tools alongside might be seen as too much work (for beginners).\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flue-bird%2Felm-no-record-type-alias-constructor-function","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flue-bird%2Felm-no-record-type-alias-constructor-function","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flue-bird%2Felm-no-record-type-alias-constructor-function/lists"}