# Extending an existing record

**URL:** <https://discourse.elm-lang.org/t/extending-an-existing-record/5330>\
**Category:** Learn\
**Created:** [March 17, 2020, 4:06am UTC](https://discourse.elm-lang.org/t/extending-an-existing-record/5330 "2020-03-17T04:06:15Z")\
**Posts on this page:** 8\
**Page:** 1

<div class="post-metadata">

**Author:** ![DullBananas](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/dullbananas/32/3709_2.png) [@DullBananas](https://discourse.elm-lang.org/u/DullBananas)\
**Post date:** [March 17, 2020, 4:06am UTC](https://discourse.elm-lang.org/t/extending-an-existing-record/5330/1 "2020-03-17T04:06:15Z")

</div>

If I have this type alias:

```elm
type alias Point2 a =
    { a
    | x : Int
    , y : Int
    }

```

How do I take a record of type `Point2 {}` and add a `z` value to turn into a `Point2 { z : Int }` record? I tried making a function like this:

```elm
makePoint3 : Int -> Point2 {} -> Point2 { z : Int }
makePoint3 z point2 =
    { point2 | z = z }

```

But the compiler says I can’t add a `z` field to a `Point2 {}` record. I know I can do this instead:

```elm
{ x = point2.x
, y = point2.y
, z = z
}

```

But I don’t want to have to do that because it’s very repetitive.

---

<div class="post-metadata">

**Author:** ![jessta](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/jessta/32/660_2.png) [@jessta](https://discourse.elm-lang.org/u/jessta)\
**Post date:** [March 17, 2020, 4:52am UTC](https://discourse.elm-lang.org/t/extending-an-existing-record/5330/2 "2020-03-17T04:52:24Z")

</div>

You will need to write out all the fields, the record update syntax no longer allows you to change the type of the record you’re updating by adding additional fields or changing the type of fields.

This was something that existed in an older version of Elm, but it caused a lot of confusion, made compiler error messages harder to do well and was a very rarely used feature of records.

People from an OOP background often see extensible records as a way to start modeling the inheritance style they’re used to but they’re really not designed for that.

Richard has a really nice talk about using extensible record types that will help you get a better idea about what they’re designed to provide.

---

<div class="post-metadata">

**Author:** ![Y0hy0h](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/y0hy0h/32/3579_2.png) [@Y0hy0h](https://discourse.elm-lang.org/u/Y0hy0h)\
**Post date:** [March 18, 2020, 3:26pm UTC](https://discourse.elm-lang.org/t/extending-an-existing-record/5330/3 "2020-03-18T15:26:04Z")

</div>

> But the compiler says I can’t add a `z` field to a `Point2 {}` record.

The compiler is right. `point2` is a `Point2 {}`. By the definition of `Point2 a` you gave, this is a record of the shape

```elm
{ x : Int,
, y : Int
}

```

Now you’re trying to update it with `{ point2 | z = z }`. But there is no `z` field! You can only update fields that already exists.

What happens in the verbose solution that you also gave is that you create a new record that has all fields, `x`, `y`, and `z`. Now since you defined `Point2 a` as an extended record, the output of `makePoint3` will also be a `Point2 a`, because it has `x` and `y` fields (and the additional `z`).

Copying each field might feel repetitive, but there is an alternative, with some downsides to ergonomics:

```elm
type alias Point3 =
    { dd : Point2 {}, -- read dd as 2D :)
    , z : Int
    }

```

Now you’re not copying fields, but keeping the `Point2 a` intact and composing it into a new record with additional information. Unfortunately, such a `Point3 a` is not a `Point2 a`, because it does not have the `x` and `y` fields (they are inside its `dd` field). So for any function that needs a `Point2 a`, you need to extract the `dd` field and pass only that.

This approach is actually slightly nicer. Sure, you have to explicitly convert between 3D and 2D points, but it’s only a quick record field call and you see all conversion. I expect that there are a few subtle bugs that you will spare yourself if you have this explicit conversion. For this to work really well, I’d even suggest not making `Point2 a` an extensible record. Just have a `type alias Point2 = { x : Int, y : Int }`.

Extensible records are nice when you only care about a small set of fields of a record. The only good case I can imagine is a `Model` record, since those necessarily will have a lot of fields. I think it is always better if you can operate on only one field at a time, but sometimes you will need to know about multiple things at once. And for those cases it can be nice to use extensible records to make working with a large struct easier. But I’d argue that it is preferrable to organize your record in a way that you do not need extensible records.

What was your reason to make `Point2 a` an extensible record in the first place?

---

<div class="post-metadata">

**Author:** ![DullBananas](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/dullbananas/32/3709_2.png) [@DullBananas](https://discourse.elm-lang.org/u/DullBananas)\
**Post date:** [March 18, 2020, 3:58pm UTC](https://discourse.elm-lang.org/t/extending-an-existing-record/5330/4 "2020-03-18T15:58:07Z")

</div>

The extensible Point2 record I showed is only a simple example of what I am trying to do. I have to parse an XML file that has a deeply nested structure of `<Value Name=".." Type=".." Value=".." />` tags and `<Values Name="..">`, which can contain other Value and Values tags. You can see an example in [this unit test](https://github.com/dullbananas/editsc/blob/master/editsc/js/tests/TestProjectFile.elm). The way I am doing it is with a pipeline where the XML data is gradually processed over time [see this file](https://github.com/dullbananas/editsc/blob/master/editsc/js/src/ProjectFile.elm). I have to do it like this because almost every step in the pipeline can fail. I decided to manually type out all the fields like @jessta said.

```elm
parseFile : String -> Result String ProjectFile
parseFile xmlString =
    xmlString
        |> XmlParser.parse
        |> Result.mapError deadEndsToString
        |> Result.map .root
        -- Now we have the root node of the XML file. The next 2
        -- functions attempt to get the attributes of the root element.
        |> Result.andThen (getAttr0 "Guid")
        -- Now we have { root, attr0 }
        |> Result.andThen (getAttr1 "Version")
        -- An attr1 field has been added
        |> Result.andThen getSubsystems
        |> Result.andThen getEntities
        -- Now we have { node, attr0, attr1, subsystems, entities }
        |> Result.mapError List.singleton
        |> Result.andThen decodeEntities
        |> Result.andThen decodeSubsystems
        |> Result.map (\a ->
            ProjectFile a.subsystems a.entities a.attr1 a.attr0)
        |> Result.mapError combineErrorStrings

```

Maybe instead of keeping on changing the type of data I am dealing with, I could just have one type of record where each field is a `Maybe` that is initially set to `Nothing` until its value is known.

---

<div class="post-metadata">

**Author:** ![jreusch](https://avatars.discourse-cdn.com/v4/letter/j/b5e925/32.png) [@jreusch](https://discourse.elm-lang.org/u/jreusch)\
**Post date:** [March 18, 2020, 5:50pm UTC](https://discourse.elm-lang.org/t/extending-an-existing-record/5330/5 "2020-03-18T17:50:09Z")

</div>

Have you thought about using [`Result.map4`](https://package.elm-lang.org/packages/elm/core/latest/Result#map4) or [`Result.Extra.andMap`](https://package.elm-lang.org/packages/elm-community/result-extra/latest/Result-Extra#andMap) instead? The basic idea is that you can construct a partial result by partially applying the constructor, so `Result.map ProjectFile getSubSystems` would conditionally give you a _partial_ `ProjectFile`, where you either filled in the subsystems, or you got an `Err` instead.

From a very quick look at the links you posted, I can’t find a place where you needed to cross-refernce something (`getEntities` doesn’t need to know about `.subsystems`), which would complicate things are little bit.

If you want to collect all the errors instead of just returning the first one, it might also be an idea to use a different `Result a` type, like the one from [the-sett/elm-error-handling](https://package.elm-lang.org/packages/the-sett/elm-error-handling/).

---

<div class="post-metadata">

**Author:** ![DullBananas](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/dullbananas/32/3709_2.png) [@DullBananas](https://discourse.elm-lang.org/u/DullBananas)\
**Post date:** [March 18, 2020, 11:32pm UTC](https://discourse.elm-lang.org/t/extending-an-existing-record/5330/6 "2020-03-18T23:32:58Z")

</div>

Thank you so much. I will try that. At first I could not figure out how those functions could possible be used to fix this problem, but now it makes sense. This will make my code so much cleaner.

---

<div class="post-metadata">

**Author:** ![DullBananas](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/dullbananas/32/3709_2.png) [@DullBananas](https://discourse.elm-lang.org/u/DullBananas)\
**Post date:** [March 19, 2020, 10:43pm UTC](https://discourse.elm-lang.org/t/extending-an-existing-record/5330/7 "2020-03-19T22:43:25Z")

</div>

I refactored the code and I did what you suggested. The diff is here: [https://github.com/dullbananas/editsc/commit/2613c0277cb2258b0ac6f295cc0615ffcb72f68b](https://github.com/dullbananas/editsc/commit/2613c0277cb2258b0ac6f295cc0615ffcb72f68b)

---

<div class="post-metadata">

**Author:** ![system](https://canada1.discourse-cdn.com/flex035/uploads/elm_lang/original/1X/50a05e53677a2c3b47776d7abd0f113eb50193a1.png) [@system](https://discourse.elm-lang.org/u/system)\
**Post date:** [March 29, 2020, 10:43pm UTC](https://discourse.elm-lang.org/t/extending-an-existing-record/5330/8 "2020-03-29T22:43:25Z")

</div>

This topic was automatically closed 10 days after the last reply. New replies are no longer allowed.
