Would Enums make a good addition to Elm?

480 views
Skip to first unread message

Robin Heggelund Hansen

unread,
Aug 2, 2017, 10:00:02 AM8/2/17
to Elm Discuss
I think most people find that Union Types are a wonderfull thing. I would argue that much of the beauty of Union Types comes from using them in a case-of statement, having the compiler complain when there is a case one haven't covered.

Strings and numbers are not so wonderfull, because they span essentially unlimited values, so case-of statements become less valuable.

However, strings and numbers still need to be used in production apps for several reasons:
1) Union Types is not supported in Json, so one needs to convert to/from strings or numbers for communicating with other systems.
2) Union Types are not comparable, and so cannot be used as keys in Sets or Dicts.

So a server might send me some json where the current user language is represented by an Int (0  and 1, norwegian and english) and the current game being played is a string ("chess", "checkers", "reversi"). It would be great to have some compiler help for these values (say, you forgot the case statement for "reversi"), AND it would be great to avoid writing encoders/decoders for the union types and converting back and forth several places.

Enums would help with this.

The way I imagine this to work is that an enum essentially represents a restricted set of String or Number. Once the compiler sees that you're working with an enum, it could help you out in case statements. And since an enum is just a string, or just a number, they can be used in the same places where one would normally use them, like as a value in a select.

One would need a way to check that a string/number matches the values of an enum though. Something like `Enum.matches : MyStringEnum -> Maybe (Enum MyStringEnum)`.

The one thing I can think of which makes this a bad addition to Elm, is that it might be a bit confusing for beginners when to use enum and when to use unions... maybe.

Anyway, what do people think of this?

Mark Hamburg

unread,
Aug 2, 2017, 5:59:24 PM8/2/17
to Elm Discuss
Something like this would probably address a "puzzle" I just resolved yesterday for one of the engineers on my team. He was testing character codes and did what one would be inclined to do in most programming languages and defined names for referencing the values — e.g., leftArrowKey : Int. He then tried to case on them and was puzzled at why the following code complained about a redundant pattern:

case keyCode of
    leftArrowKey -> ...
    rightArrowKey -> ...
    _ -> ...

Of course, what's happening here is that the mentions of leftArrowKey and rightArrowKey in the case statement are each creating new pattern variables that have nothing to do with the definitions elsewhere of those names. So, Elm's error message is correct but in this case not helpful. More to the point, however, the fixes were to either just use the constant values directly or to replace the case expression with a sequence of if expressions. Neither of those are particularly attractive. The conclusion of our exchange was that it would be nice to have some form of named constants that could be used in case expressions, but adding them would run counter to Elm's minimalist inclinations.

Mark

--
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+unsubscribe@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Richard Feldman

unread,
Aug 2, 2017, 7:27:28 PM8/2/17
to Elm Discuss
Mark - this seems like a great addition to the error message catalog! I've heard of people encountering this too.

Seems like it should be easy enough to detect, and the compiler could either provide a concise explanation of what's going on or link to a longer explanation.

Richard Feldman

unread,
Aug 2, 2017, 7:32:14 PM8/2/17
to Elm Discuss
However, strings and numbers still need to be used in production apps for several reasons:
1) Union Types is not supported in Json, so one needs to convert to/from strings or numbers for communicating with other systems.
2) Union Types are not comparable, and so cannot be used as keys in Sets or Dicts.

Worth noting that (1) could also be fixed by having a deriving-esque way to generate encoders and decoders automatically, so long as it worked for union types as well as records. (Granted, the serialized format is less obvious with union types compared to records.)

Also worth noting that (2) is already slated to be fixed directly. It's a lower priority than other things in the hopper right now, but it'd probably still be higher priority than adding an enum feature. 😄

Robin Heggelund Hansen

unread,
Aug 3, 2017, 4:15:44 AM8/3/17
to Elm Discuss
Regarding (2), I was not aware of this. Cool =)

Having a deriving-esque way to generate encoders and decoders automagically would be great, and would go a long way in solving the hassle. (2) would also make my life easier.

The problem I see with both deriving-esque auto coders (DEAC, patent pending) and comparable union types, is the difficulty of implementation. DEAC's seem like an advanced language feature that will take a while to get into the language. Does a union type require that all fields/members are comparable as well? Or will it only work for member-less/valueless union types? As a reader, how easy is it to tell if a union type is comparable, and is it obvious when one is lesser than another? It's not straight forward from a design perspective.

Enums have very clear encoding/decoding and comparability semantics, which is why I think they would be a good addition.

Of course, it could be that DEAC's and comparable union types is the better way (TM) and one should wait for that instead.

Francesco Orsenigo

unread,
Aug 3, 2017, 10:13:11 AM8/3/17
to Elm Discuss
Also worth noting that (2) is already slated to be fixed directly. It's a lower priority than other things in the hopper right now, but it'd probably still be higher priority than adding an enum feature. 😄

This is fantastic news! 

Mark Hamburg

unread,
Aug 3, 2017, 12:12:08 PM8/3/17
to elm-d...@googlegroups.com
I thought about whether this was an opportunity to improve error messages but wasn't sure what the improvement would be. Is it detection that a previous case already matched everything? Is it specifically noting that the case involved a variable that shadows an existing variable? Or is this something better covered with a warning rather than an error about variable shadowing? For example:

Warning: The pattern variable "foo" shadows the definition at line 342. Patterns can only use constructors and explicit string and numeric constants. If you need to compare against a variable, use an if expression. If you intended to use a pattern variable, you can eliminate this warning by choosing a different name.

Mark

On Wed, Aug 2, 2017 at 4:27 PM Richard Feldman <richard....@gmail.com> wrote:
Mark - this seems like a great addition to the error message catalog! I've heard of people encountering this too.

Seems like it should be easy enough to detect, and the compiler could either provide a concise explanation of what's going on or link to a longer explanation.

--
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.

Richard Feldman

unread,
Aug 4, 2017, 10:54:30 AM8/4/17
to Elm Discuss
The problem I see with both deriving-esque auto coders (DEAC, patent pending) and comparable union types, is the difficulty of implementation. DEAC's seem like an advanced language feature that will take a while to get into the language.

Having talked to Evan about it, it seems like the design work is what's hard. The implementation is actually not a ton of work, apparently.

That said, the design work is hard. 😉

Does a union type require that all fields/members are comparable as well?

I think that'd necessarily be true, yeah. But if union types and records are comparable (assuming they hold only comparable types), I think you end up with something like "functions aren't comparable, nor are Kernel-implemented values such as Value, Task, Cmd, and Html, but everything else is."
 
As a reader, how easy is it to tell if a union type is comparable

Probably not easy, would be my guess. It's unclear to me how big of a deal that would be though.
 
and is it obvious when one is lesser than another?

Apparently the way Haskell does this is by using the order of the tags (e.g. for type Maybe a = Just a | Nothing, we could say that Just "foo" > Nothing because Just came before Nothing in the type declaration), which seems fine to use since that ordering is currently arbitrary and meaningless. Similarly, record fields could be compared alphabetically by field name (since record fields definitionally have no ordering; if you needed them to be compared in a certain order, you could use a union type).

Considering the main (only?) reason people want them to be comparable is so they can be used in Sets and Dict keys, it doesn't seem hugely important how they compare, so long as they can be for purposes of internal data structure implementation details. I could be missing something though!

It's not straight forward from a design perspective.

I totally agree! 😄

Richard Feldman

unread,
Aug 4, 2017, 11:04:07 AM8/4/17
to Elm Discuss
Having a deriving-esque way to generate encoders and decoders automagically would be great, and would go a long way in solving the hassle. (2) would also make my life easier.
The problem I see with both deriving-esque auto coders (DEAC, patent pending) and comparable union types, is the difficulty of implementation. DEAC's seem like an advanced language feature that will take a while to get into the language.

Oh yeah - worth noting that this implementation already exists for records. Ports do it. If you send a record out a port, or bring it in through a port, under the hood what's happening is an encoder (or decoder, respectively) gets generated automatically from the type.

Doing it for union types as well would largely be a matter of designing a nice serialization and deserialization format; the type information is just as easy to access as it is for records. 😉

Ambrose Laing

unread,
Aug 4, 2017, 12:06:53 PM8/4/17
to Elm Discuss
It seems like a minor detail that may come as a matter of course, but I'd like to request that Bool's should also be comparable.  If Bools are not comparable but you embed a Bool inside a tuple or a List or a Record, that makes the larger type also not comparable.  Unless you decide to ignore the Bool's but I think that is a less useful design choice IMO.

Another general issue is what happens if the programmer does not like the ordering provided by default for a particular type?  Ie. if we are in a world where False < True, but I would prefer to use True < False, what do I do.  Can there be a special function name ("compare" would be the most obvious choice) such that if I define my own version of that function for any given type, then it is used rather than the native default?  I think that would be especially useful if you don't want to compare records alphabetically by field name.

Eg.  I want to sort records by name first and age second -- records of the type { name: String, age: Int }.  I don't want to have to force myself to rename them creatively to get the sort I want eg: { appelation: String, maturity: Int } or worse {aName: String, zAge: Int}.

In this instance the ability to override the sort order by defining a compare function would be helpful.

Alternatively for records, one could use the fact that the listing order otherwise carries no information, and have the sort order for 
records be implied by the listing order.  Just like unions in Haskell.  So:

{name: String, age: Int} 

would sort automatically by name first and age second.  But it would remain legal to write {age = 22, name="Bob"}.  This is controversial because it retains the fact that by definition the ordering of fields in the record is irrelevant, and yet partially gives them meaning under special circumstances.  There might also be issues if
you define the record one way, and persist the data somehow, and then read the data back in after compiling with a source code which changes the listing order of
the fields.  Nothing should change.  Yet, this solution will cause changes in sorting behavior.

All things considered, it might be better to allow the programmer to define a compare function and have that used automatically for Sets and Dicts.  Kind of typeclass-ish, but it may not necessarily require typeclasses to be exposed in the language.  And let the listing order of the field names in the source code have no significance.

Ambrose Laing

unread,
Aug 4, 2017, 12:48:26 PM8/4/17
to Elm Discuss
I just realized that the sortBy function already allows you to sort a list by any sort criterion of your choice.  Which removes some of my concerns in the previous post.

Mark Hamburg

unread,
Aug 4, 2017, 1:20:06 PM8/4/17
to Elm Discuss
Definitely the big reason from my perspective to make more things comparable is to allow their use with dictionaries and sets. For that usage, the comparison could even be compiler dependent (or even execution dependent) but I'm sure that someone would build code that depended on properties of some particular implementation.

Mark

P.S. Since this forum likes examples, rather than passing around naked strings, I pass around type AccountID = AccountID String. This is great from a type-checking perspective but means that I can't have a dictionary keyed by AccountID and instead I need to unwrap the value to get the underlying string. That's not too bad, but I recently had need to fold over such a dictionary and so found myself faced with either having a function to force convert a String into an AccountID or storing the original wrapped AccountID along with the value. Neither solution was particularly pretty — one because it is a temptation to undermine type-safety and one because it complicates the dictionary for all other uses.

--
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+unsubscribe@googlegroups.com.
Reply all
Reply to author
Forward
0 new messages