Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Are extensible records useless in Elm 0.19?

Tags:

elm

Extensible records were one of the most amazing Elm's features, but since v0.16 adding and removing fields is no longer available. And this puts me in an awkward position.

Consider an example. I want to give a name to a random thing t, and extensible records provide me a perfect tool for this:

type alias Named t = { t | name: String }

„Okay,“ says the complier. Now I need a constructor, i.e. a function that equips a thing with specified name:

equip : String -> t -> Named t
equip name thing = { thing | name = name }  -- Oops! Type mismatch

Compilation fails, because { thing | name = ... } syntax assumes thing to be a record with name field, but type system can't assure this. In fact, with Named t I've tried to express something opposite: t should be a record type without its own name field, and the function adds this field to a record. Anyway, field addition is necessary to implement equip function.

So, it seems impossible to write equip in polymorphic manner, but it's probably not a such big deal. After all, any time I'm going to give a name to some concrete thing I can do this by hands. Much worse, inverse function extract : Named t -> t (which erases name of a named thing) requires field removal mechanism, and thus is not implementable too:

extract : Named t -> t
extract thing = thing  -- Error: No implicit upcast

It would be extremely important function, because I have tons of routines those accept old-fashioned unnamed things, and I need a way to use them for named things. Of course, massive refactoring of those functions is ineligible solution.

At last, after this long introduction, let me state my questions:

  1. Does modern Elm provides some substitute for old deprecated field addition/removal syntax?

  2. If not, is there some built-in function like equip and extract above? For every custom extensible record type I would like to have a polymorphic analyzer (a function that extracts its base part) and a polymorphic constructor (a function that combines base part with additive and produces the record).

  3. Negative answers for both (1) and (2) would force me to implement Named t in a more traditional way:

    type Named t = Named String t
    

    In this case, I can't catch the purpose of extensible records. Is there a positive use case, a scenario in which extensible records play critical role?

like image 684
const.grigoryev Avatar asked Apr 06 '19 16:04

const.grigoryev


3 Answers

Type { t | name : String } means a record that has a name field. It does not extend the t type but, rather, extends the compiler’s knowledge about t itself.

So in fact the type of equip is String -> { t | name : String } -> { t | name : String }.

What is more, as you noticed, Elm no longer supports adding fields to records so even if the type system allowed what you want, you still could not do it. { thing | name = name } syntax only supports updating the records of type { t | name : String }.

Similarly, there is no support for deleting fields from record.

If you really need to have types from which you can add or remove fields you can use Dict. The other options are either writing the transformers manually, or creating and using a code generator (this was recommended solution for JSON decoding boilerplate for a while).

And regarding the extensible records, Elm does not really support the “extensible” part much any more – the only remaining part is the { t | name : u } -> u projection so perhaps it should be called just scoped records. Elm docs itself acknowledge the extensibility is not very useful at the moment.

like image 69
Jan Tojnar Avatar answered Oct 01 '22 13:10

Jan Tojnar


You could just wrap the t type with name but it wouldn't make a big difference compared to approach with custom type:

type alias Named t = { val: t, name: String }

equip : String -> t -> Named t
equip name thing = { val = thing, name = name }

extract : Named t -> t
extract thing = thing.val
like image 36
Karol Samborski Avatar answered Oct 01 '22 14:10

Karol Samborski


Is there a positive use case, a scenario in which extensible records play critical role?

Yes, they are useful when your application Model grows too large and you face the question of how to scale out your application. Extensible records let you slice up the model in arbitrary ways, without committing to particular slices long term. If you sliced it up by splitting it into several smaller nested records, you would be committed to that particular arrangement - which might tend to lead to nested TEA and the 'out message' pattern; usually a bad design choice.

Instead, use extensible records to describe slices of the model, and group functions that operate over particular slices into their own modules. If you later need to work accross different areas of the model, you can create a new extensible record for that.

Its described by Richard Feldman in his Scaling Elm Apps talk:

https://www.youtube.com/watch?v=DoA4Txr4GUs&ab_channel=ElmEurope

I agree that extensible records can seem a bit useless in Elm, but it is a very good thing they are there to solve the scaling issue in the best way.

like image 28
user2800708 Avatar answered Oct 01 '22 13:10

user2800708