Discussion:
[elm-discuss] Is there a way of accessing the type constructor of a type that is defined using an extensible record?
Will Sewell
2014-12-14 19:23:47 UTC
Permalink
Example:

type alias Base a = { a | x : Int }
type alias Sub = Base { y : Int }

f = Base -- works
g = Sub -- fails

Is there a way to reference Sub?
--
You received this message because you are subscribed to the Google Groups "Elm Discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elm-discuss+***@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
Jeff Smits
2014-12-14 19:44:18 UTC
Permalink
Sounds like a bug if that fails to compile.
Post by Will Sewell
type alias Base a = { a | x : Int }
type alias Sub = Base { y : Int }
f = Base -- works
g = Sub -- fails
Is there a way to reference Sub?
--
You received this message because you are subscribed to the Google Groups
"Elm Discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an
For more options, visit https://groups.google.com/d/optout.
--
You received this message because you are subscribed to the Google Groups "Elm Discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elm-discuss+***@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
Hassan Hayat
2014-12-14 19:55:19 UTC
Permalink
Could this be related to this:

If you write the following code:

type alias Point2D a = {
x : Float,
y : Float
}

type alias Point3D = Point2D {z : Float}

p = Point3D 1 2 3

you get the error:

Error on line 9, column 5 to 12:
Could not find variable 'Point3D'.
Post by Jeff Smits
Sounds like a bug if that fails to compile.
Post by Will Sewell
type alias Base a = { a | x : Int }
type alias Sub = Base { y : Int }
f = Base -- works
g = Sub -- fails
Is there a way to reference Sub?
--
You received this message because you are subscribed to the Google Groups
"Elm Discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an
For more options, visit https://groups.google.com/d/optout.
--
You received this message because you are subscribed to the Google Groups "Elm Discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elm-discuss+***@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
Max Goldstein
2014-12-14 20:07:45 UTC
Permalink
This has to do with how record type aliases *in the type language* become
constructor functions *in the term language*.

type alias Base a = { a | x : Int }
type alias Sub = Base { y : Int }

f : Int -> a -> { a | x : Int }
f = Base

This compiles. What's slightly confusing is that Base the term takes the
Int first and the extendable part second; Base the type takes the type
parameter immediately. The other confusing thing is, just looking at the
second line, Base could be a tag (union type, ADT until this week). But
it's actually an extendible record type. It's possible that's confusing the
parser as well as me. If Base *was* a tag, you would expect it to *not* be
a valid term.

But as it stands, we'd expect Sub to take the x and then the y. So I think
I've convinced myself this is a bug, too.
--
You received this message because you are subscribed to the Google Groups "Elm Discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elm-discuss+***@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
Evan Czaplicki
2014-12-14 20:17:15 UTC
Permalink
It's not a bug. Record constructors are only generated when you are
defining a record directly. This has come up on the list before I believe.

As it is, you can generate the code necessary just from the syntax. Base
takes these fields in this order, and maybe it can be extended. It's not
totally obvious to me how the order of arguments would work otherwise.

Longer term, there is the type-directed macros proposal which may be the
principled solution to this.
Post by Max Goldstein
This has to do with how record type aliases *in the type language* become
constructor functions *in the term language*.
type alias Base a = { a | x : Int }
type alias Sub = Base { y : Int }
f : Int -> a -> { a | x : Int }
f = Base
This compiles. What's slightly confusing is that Base the term takes the
Int first and the extendable part second; Base the type takes the type
parameter immediately. The other confusing thing is, just looking at the
second line, Base could be a tag (union type, ADT until this week). But
it's actually an extendible record type. It's possible that's confusing the
parser as well as me. If Base *was* a tag, you would expect it to *not* be
a valid term.
But as it stands, we'd expect Sub to take the x and then the y. So I think
I've convinced myself this is a bug, too.
--
You received this message because you are subscribed to the Google Groups
"Elm Discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an
For more options, visit https://groups.google.com/d/optout.
--
You received this message because you are subscribed to the Google Groups "Elm Discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elm-discuss+***@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
Joe Fiorini
2015-01-02 20:29:18 UTC
Permalink
I just came across this issue myself. What I find weird about it is that
the syntax for creating a record type is no different whether or not it's
extendible. By that I mean in the following example:

type alias Foo f = { f | foo : String }
type alias Bar = Foo { bar : String }
type alias Baz = { baz : String }

The only difference is the fact that I pass one through a type constructor
and the other I don't. Since the latter example will produce its own
constructor I had no real indicator here that the former one wouldn't. To
me it seemed like the "type alias" keyword is what was generating the
constructor. Based on my reading in this thread, the compiler does some
magic based on what's on the right side of the equals to determine whether
or not to create a constructor?

I would have expected it Bar to have a type constructor that just takes the
parent properties first, followed by the child properties. As it is, in
order to use this with functions that I can pass the constructor into (like
the Json.Decode.object* variants) I now have to create my own function to
serve as the flattened constructor like:

mkFoo foo bar =
{ foo = foo
, bar = bar
}

which isn't a big deal for an object like this, but in real life objects
tend to be much larger; in my app I'm now adding:

mkBox position size label originalLabel key isEditing isSelected isDragging
selectedIndex borderSize =
{ position = position
, size = size
, label = label
, originalLabel = originalLabel
, key = key
, isEditing = isEditing
, isSelected = isSelected
, isDragging = isDragging
, selectedIndex = selectedIndex
, borderSize = borderSize
}

This is a lot of repetition and mental overhead the next time my types
change.

Is my suggestion of using the order the parents are specified to determine
parameter order a good workaround? Or this just too hard to do right now?
Post by Evan Czaplicki
It's not a bug. Record constructors are only generated when you are
defining a record directly. This has come up on the list before I believe.
As it is, you can generate the code necessary just from the syntax. Base
takes these fields in this order, and maybe it can be extended. It's not
totally obvious to me how the order of arguments would work otherwise.
Longer term, there is the type-directed macros proposal which may be the
principled solution to this.
Post by Max Goldstein
This has to do with how record type aliases *in the type language* become
constructor functions *in the term language*.
type alias Base a = { a | x : Int }
type alias Sub = Base { y : Int }
f : Int -> a -> { a | x : Int }
f = Base
This compiles. What's slightly confusing is that Base the term takes the
Int first and the extendable part second; Base the type takes the type
parameter immediately. The other confusing thing is, just looking at the
second line, Base could be a tag (union type, ADT until this week). But
it's actually an extendible record type. It's possible that's confusing the
parser as well as me. If Base *was* a tag, you would expect it to *not* be
a valid term.
But as it stands, we'd expect Sub to take the x and then the y. So I
think I've convinced myself this is a bug, too.
--
You received this message because you are subscribed to the Google Groups
"Elm Discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an
For more options, visit https://groups.google.com/d/optout.
--
You received this message because you are subscribed to the Google Groups "Elm Discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elm-discuss+***@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
Joe Marty
2017-07-13 15:03:57 UTC
Permalink
Wow!! I'm glad this thread is here. I was so confused by this for a while
(nobody on the slack channel seemed to expect or know about this
limitation). I agree that it would be better to just choose any consistent
method for defining the order of parameters for a constructor. If I got
the order wrong somehow, at least that's something I would
expect/understand more easily, and would be easily fixed (because I'd get a
nice, helpful error from the compiler), and most importantly, it would
provide the convenience of a constructor without having to define and
maintain one, as noted below by Joe Fiorini. I wouldn't really care what
order the parameters need to be in, as long as I get a free constructor! :)
(And if I don't like the order, *then* I can resort to making my own
constructor)
Post by Joe Fiorini
I just came across this issue myself. What I find weird about it is that
the syntax for creating a record type is no different whether or not it's
type alias Foo f = { f | foo : String }
type alias Bar = Foo { bar : String }
type alias Baz = { baz : String }
The only difference is the fact that I pass one through a type constructor
and the other I don't. Since the latter example will produce its own
constructor I had no real indicator here that the former one wouldn't. To
me it seemed like the "type alias" keyword is what was generating the
constructor. Based on my reading in this thread, the compiler does some
magic based on what's on the right side of the equals to determine whether
or not to create a constructor?
I would have expected it Bar to have a type constructor that just takes
the parent properties first, followed by the child properties. As it is, in
order to use this with functions that I can pass the constructor into (like
the Json.Decode.object* variants) I now have to create my own function to
mkFoo foo bar =
{ foo = foo
, bar = bar
}
which isn't a big deal for an object like this, but in real life objects
mkBox position size label originalLabel key isEditing isSelected
isDragging selectedIndex borderSize =
{ position = position
, size = size
, label = label
, originalLabel = originalLabel
, key = key
, isEditing = isEditing
, isSelected = isSelected
, isDragging = isDragging
, selectedIndex = selectedIndex
, borderSize = borderSize
}
This is a lot of repetition and mental overhead the next time my types
change.
Is my suggestion of using the order the parents are specified to determine
parameter order a good workaround? Or this just too hard to do right now?
Post by Evan Czaplicki
It's not a bug. Record constructors are only generated when you are
defining a record directly. This has come up on the list before I believe.
As it is, you can generate the code necessary just from the syntax. Base
takes these fields in this order, and maybe it can be extended. It's not
totally obvious to me how the order of arguments would work otherwise.
Longer term, there is the type-directed macros proposal which may be the
principled solution to this.
Post by Max Goldstein
This has to do with how record type aliases *in the type language* become
constructor functions *in the term language*.
type alias Base a = { a | x : Int }
type alias Sub = Base { y : Int }
f : Int -> a -> { a | x : Int }
f = Base
This compiles. What's slightly confusing is that Base the term takes the
Int first and the extendable part second; Base the type takes the type
parameter immediately. The other confusing thing is, just looking at the
second line, Base could be a tag (union type, ADT until this week). But
it's actually an extendible record type. It's possible that's confusing the
parser as well as me. If Base *was* a tag, you would expect it to *not* be
a valid term.
But as it stands, we'd expect Sub to take the x and then the y. So I
think I've convinced myself this is a bug, too.
--
You received this message because you are subscribed to the Google
Groups "Elm Discuss" group.
To unsubscribe from this group and stop receiving emails from it, send
For more options, visit https://groups.google.com/d/optout.
--
You received this message because you are subscribed to the Google Groups "Elm Discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elm-discuss+***@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
Loading...