Polymorphic Record Type in Elm

Viewed 516

Is the following possible in Elm?

func : a -> {a | id : Int}
func x = { x | id = 123 }

This fails to compile because a is too polymorphic; it thinks it can be anything, including non-record types. How do I tell the compiler that a is a record type, but one we don't know ANY of the fields for? (Honestly, I would have though the {a | id : Int} was enough).

I have tried...

type alias Record a = {a}
func : Record a -> { a | id : Int }

func : {a} -> {a | id : Int}
func x = { x | id = 123 }

Both of which fails with syntax errors. Is it possible to say "this type is a record, but I don't know anything else" to Elm?

To address the response below:

If it was a record without any specific fields, you wouldn't be able to do anything record-like with it anyway, so it doesn't seem useful to be able to express that.

I am attempting to do something record-like with a record of unknown fields in my example, so saying nothing can be done with them is incorrect.

You can't dynamically add fields to a record.

I'm not dynamically adding a field to a record, I am creating a new record that matches an existing except it has an id field with value 123.

That's just not a thing records do, probably because it would be really awkward if a already had an id field.

This seems like the actual meat of your reasoning, and I don't find it terribly convincing. Why would that be awkward at all? It seems like working that way would be by far the most obvious way to work?

If this isn't what you want, then it seems this is a case of the XY problem

Just an aside, but every time I bump into annoyances/weaknesses in Elm's type system it's somehow my fault not Elms.

3 Answers

No, Elm does not have a way for a type annotation to indicate that a type is a record without specifying any fields. However, even without type annotations, Elm does not have a syntax to do what you're wanting. { a | id = 123 } is the record update syntax, it does not support adding fields to a record (and, as of Elm 0.19, does not support changing the type of a field, either)

So far as I know, Elm does not have a syntax which will allow you to generate a copy of the record with an additional field.

If it was a record without any specific fields, you wouldn't be able to do anything record-like with it anyway, so it doesn't seem useful to be able to express that.

You can't dynamically add fields to a record. That's just not a thing records do, probably because it would be really awkward if a already had an id field. So in order to assign id you need to know that the record has an id field. Hence the correct type for the function is:

func : {a | id : Int} -> {a | id : Int}

If this isn't what you want, then it seems this is a case of the XY problem

The above answers are correct and more idiomatic responses, but I think you could accomplish what you are after by defining the type alias of the record with the new field. Of course you would have to define the union type C for all possible record types.

type alias A = { a : String }
type alias B = { a : Int }
type C = Foo A
       | Bar B

type alias D = { a : C, d : Int }
fn : C -> Int -> D 
fn a y = D a y 
Related