[Proposal] Add Record Module to Core Libary

Hi everyone, I have this idea in my mind, of a record module within the core library. In my mind it would be a big improvement to the language. I’ve not though about if what I’m asking is actually possible, so I’d love to discuss how and why these suggestions can’t or can be done.


So let’s assume for a moment, that we have a module called Record that can manipulate records using some internal elm magic. Here are some functions that i personally believe would be nice to have:

Record.construct

Record.construct would be a better version of the existing Record constructors. Essentially making it possible to parse Json files without needing to specify a type alias.

decoder :
  D.Decoder
    { name : String
    , percent : Float
    , per100k : Float
    }
decoder =
  D.map3 Record.construct
    (D.field "name" D.string)
    (D.field "percent" D.float)
    (D.field "per100k" D.float)

Record.combine

Record.combine would reintroduce a feature from 1.18: combining records. The difference here would be that both records must have unique field names. This lets us partially create records. When combined with extendable records this makes a lot of sense

  { x = 2
  , y = 4
  }
    |> rotateBy 90
    |> Record.combine { z = 0}

Record.map

For Records with the same type for all fields, it should be possible to use map and fold. I’ve seen this in the wild, where I use records for bound lists, but then i need to also introduce a new map function with it.

Record.toList

Again for Records where all types are the same, we can transform the record into a list. Note that in this case the list would be sorted. I need this a lot for using cell automatas. For example in Game of Life, I used a record to represent the neighbours of a cell. Now I need to check if exactly 2 or 3 neighbours are present.

neighbours
|> Record.toList
|> List.filter (\maybe -> maybe /= Nothing)
|> List.length
|> \n -> (n == 3) || (n == 2)

Doing the same without this feature is a lot of boiler plating. (There are 8 neighbours for a given cell)


If you can dream of any additional function that should be added to this imaginary module, i’d like to hear them. :slight_smile:

Think about the types that your various functions would need to have. They cannot be expressed with elm’s current type system. For instance Record.combine : { a | } -> { b | } -> ?. How does it deal with duplicate keys (note that the keys are not present in the type, so how could you even constrain them)

I think ultimately records are a little inflexible. That’s by design, and provides many guarantees. Changing that would require a significant amount of meta-programming. (e.g. to turn string literals into record keys).

I think a more interesting approach is to see whether you could write editor tooling to automatically derive these functions for specific record types.

6 Likes

When you want a flexible Record just use Dict?

4 Likes

As @folkertdev said, I think the types of your various functions aren’t expressible in Elm’s type language. Which means these various functions would have to be added as part of the language. As such it’s a bit harder to argue for their inclusion, because you’re increasing the complexity of the language (and associated tools). Hence the gain has to be pretty impressive. To argue for that, what you need to do is find a bunch of existing code that would be improved by your proposed changes.

For example, I could see date-time related functions potentially using your Record.map or Record.toList. Eg. you might have a record of days of the week: { monday : Day, tuesday : Day, ... etc.}, where perhaps you want to write some function that maps each of the days of the week to, say it’s name say perhaps to make the column header row in a table of days.

Just specifically about Record.combine that’s an interesting proposal. You would have to revisit why this kind of record update was disallowed between 0.18 and 0.19, but I think it was (at least partly) because it was too easy to make a mistake when doing something like: { point | xx = 0} when you meant { point | x = 0}, sometimes the compiler would help you out with that, but not always. However your proposal would get around that by explicitly being about ‘extending’ a record, so you would use one syntax for updating an existing field { point | x = 0} and different one for extending it Record.combine point { z = 0 }. Still the key point is to find a bunch of existing code that would be improved by this addition.

1 Like

Dicts are indeed more flexible records, but they also differ from records by enforcing that all “properties” are optional. I’ve been in a few situations where I wished I had something like Record.map for that reason at least :slightly_smiling_face:

1 Like

(+1 to what others have said wrt types and complexity)

Wrt the construct function in particular, you would need the compiler to complete infer its type, making mistakes easier, error messages worse, type inference slower and more complex.

Wrt combine: I’d actually like to have some syntax back for extensible records. It’s a rare feature but still useful those rare times. Evan said the typechecker / typesystem still supports them so it could be feasible. Iirc they were removed because rarely used, almost always able to being replaced by a bit of boilerplate and lead to worse error messages

1 Like

Yeah, while trying to understand what exactly the type of such a function would be, I noticed that i need to have some meta-level type to reason about keys.

I’m currently working with Rust and seeing how many shortcuts can be done by using the derive feature. I’m not quite sure though, how this would work in elm. Maybe some tooling that can guess the meaning of a function by its type, and requesting to generate utility functions, if it thinks they do not yet exist.

Yeah, I’ve never really expected that this would actually be implementable, but I can dream :drooling_face:

That’s interesting to know.

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