List of all members of a union type

1,044 views
Skip to first unread message

Matthew Buscemi

unread,
May 2, 2017, 11:44:34 PM5/2/17
to Elm Discuss
I have run into a problematic use case a couple of times, and based on Slack discussions earlier today, it seems I'm not alone.

The situation occurs when, for some reason, I need to maintain a list of all constructors of a union type. The example given in Slack by mltsy:

type State = Alabama | Alaska | Arizona | Arkansas | ...

allStates
= [ Alabama, Alaska, Arizona, Arkansas, ...]

stateSelectBox
: List State -> Html Msg
stateSelectBox states
=
  let stateValues
= List.map toString states
 
in ...

In the example above, simply turning allStates into a list of strings would be fine for this particular use case, but in the context of a larger system, it could be inadequate–other functions besides stateSelectBox may want to utilize the power of performing a case over the union type. However, the above solution is also structurally undesirable–if a new type is added to State, then the programmer must also remember to update allStates separately. The duplication and tight coupling are disconcerting, and yet I can't see a viable alternative that preserves the power of types without incurring the duplication.

I have wondered if a language-level construct that takes a union type and returns a list of all constructors for that type would be appropriate for Elm. Just a thought.

- Matt


Joey Eremondi

unread,
May 2, 2017, 11:56:20 PM5/2/17
to elm-d...@googlegroups.com
The problem is, this would only work when all the constructors had 0 arguments , or arguments of the same type. Otherwise, what would the type of such a list be?

Things like macros or Template Haskell allow this, or Haskell style Generics. But Elm doesn't have the machinery for things like this, without type classes or macros.

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

Peter Damoc

unread,
May 3, 2017, 1:19:26 AM5/3/17
to Elm Discuss
On Wed, May 3, 2017 at 6:56 AM, Joey Eremondi <joey.e...@gmail.com> wrote:
The problem is, this would only work when all the constructors had 0 arguments , or arguments of the same type. Otherwise, what would the type of such a list be?

But if we would have a function like: 

defaultValues : Type a -> List a

where Type a would be some compiler magic value that contains the type info of a, wouldn't we be able to say 

type WeekDay = Mon | Tue | Wed | Thu | Fri | Sat | Sun 

allDays = defaultValues WeekDay 


defaultValues would return a list of all the default values for all the constructors. So, if the constructor takes 0 parameters would return the tag, if it takes 1 value would return the value created by applying the tag to the default value of the type it contains (eg `Some Int` would return `Some 0`). The default value for a type can be by convention the first tag for tagged unions. For the number it would be 0 and for String "".

 


--
There is NO FATE, we are the creators.
blog: http://damoc.ro/

Charles Scalfani

unread,
May 3, 2017, 10:14:22 AM5/3/17
to Elm Discuss
Not all types have default values. What's the default value of another Union Type?

Max Goldstein

unread,
May 3, 2017, 11:17:09 AM5/3/17
to Elm Discuss
Building on Charles, how would this handle recursive union types?

Charles Scalfani

unread,
May 3, 2017, 11:34:58 AM5/3/17
to Elm Discuss
I think the problem here is that you're using types as an Enum. There's a bit of a difference between types and Enums.

Enums are a set of symbolic values that limit the values that variables of that Enum can take on.

Types can act like Enums like you've shown, but they can also be functions. So they are really a different animal.

What's really missing is a way to limit values a variable can take on at the language level. (Then you could use a case statement with the variable). I'm no language expert, but I suspect there are languages that have some form of constraint-based types.

This is the piece that's missing in Elm. I suspect that this feature is either very difficult to do or a low priority since its use case is rare or maybe it's not even on Evan's radar.

Whatever the case may be, I've have always wanted constraint-based types and would love to see a language properly implement this (assuming it wouldn't bloat the language unnecessarily).

Peter Damoc

unread,
May 3, 2017, 11:52:05 AM5/3/17
to Elm Discuss
On Wed, May 3, 2017 at 6:17 PM, Max Goldstein <maxgol...@gmail.com> wrote:
Building on Charles, how would this handle recursive union types?

This can be handled as a convention at compiler level. 

A new restricted type variable could be created, let's call it "enumerable" and only non-recursive, non-function types be "enumerable". 
If an non-enumerable type is used, the compiler throws an error the same way it throws an error if a non-comparable type is used as a Dict key. 



-- 

Charles Scalfani

unread,
May 3, 2017, 12:00:07 PM5/3/17
to Elm Discuss
Adding this to the language feels like how other languages evolve, which is haphazardly.

I rather see a more general solution to this problem via some type of constraint-based type. For example, imagine you can define something as String but with a constraint that limits the values they can contain. This would solve all current problems we have with using types as Enums. We'd have exhaustive case statements checking and since it's already a String converting to String is unnecessary.

Peter Damoc

unread,
May 3, 2017, 12:12:23 PM5/3/17
to Elm Discuss
On Wed, May 3, 2017 at 7:00 PM, Charles Scalfani <csca...@gmail.com> wrote:
Adding this to the language feels like how other languages evolve, which is haphazardly.

I rather see a more general solution to this problem via some type of constraint-based type. For example, imagine you can define something as String but with a constraint that limits the values they can contain. This would solve all current problems we have with using types as Enums. We'd have exhaustive case statements checking and since it's already a String converting to String is unnecessary.

A general solution would bring its own set of issues as all general solutions tend to be abused. As such, a general solution needs way way more thought and design. 

Anyway, I'm not arguing that what I said above is necessarily a good idea. I haven't given it that much thought. 
All I say is that it is one conceivable way. 
The main advantage I see is that the language doesn't really change, it's just a convention that turns some already expressible types into something that can do a little bit more. 



Ambrose Laing

unread,
May 3, 2017, 2:52:36 PM5/3/17
to Elm Discuss
If you really want a solution and want it now, here is a solution using the unix-based m4 text/macro processor -- don't know if this has been mentioned in the other threads on this topic.   It should be possible to write a file like so (maybe named YourFile.elm.m4):


## The following is the only line that lists the state names!
define(`stateList', ``Alabama', `Alaska', `Arizona', `Arkansas'')dnl

type AState = m4AltSep(stateList)

allAStates = m4ListStates(stateList)

allAStateNames = m4ListStrings(stateList)

When you run it through m4 with the right macro definitions, you will get this:


## The following is the only line that lists the state names!

type AState = Alabama | Alaska | Arizona | Arkansas

allAStates = [ Alabama, Alaska, Arizona, Arkansas ]

allAStateNames = [ "Alabama", "Alaska", "Arizona", "Arkansas" ]


And the required definitions are (maybe this can be in elmdefs.m4):

define(`m4foreach',`ifelse(eval($#>2),1,
`pushdef(`last_$1',eval($#==3))dnl
`'pushdef(`$1',`$3')$2`'popdef(`$1')dnl
`'popdef(`last_$1')dnl
`'ifelse(eval($#>3),1,`$0(`$1',`$2',shift(shift(shift($@))))')')')dnl
define(`m4AltSep', `m4foreach(`xstate', `xstate`'ifelse(last_xstate,0,` | ')',$@)')dnl
define(`m4ListStates', `[ m4foreach(`xstate', `xstate`'ifelse(last_xstate,0,`, ')',$@) ]')dnl
define(`m4ListStrings', `[ m4foreach(`xstate', `"xstate"`'ifelse(last_xstate,0,`, ')',$@) ]')dnl

The macros are adapted from M. Breen's page especially the section on loops.
The command that makes it work:

cat elmdefs.m4 YourFile.elm.m4 | m4 > YourFile.elm

The price you pay is having to somehow integrate this command into your build flow -- it is easy enough using unix make.  And if your build tool would need to understand that when the .elm.m4 file changes, the .corresponding elm file should be rebuilt.  And of course if you are using elm-format that will work at the .elm level and it will not understand the .elm.m4 level.  Finally because it is a source-level modification, it does not resolve any of the semantic issues that have been raised in this thread.  It just deals with the very narrow enum case.  Given this extra work to set this up, some may opt to just live with the risk of getting your lists/enumerations out of sync, ... this can only be practical when your codebase has a lot of Enums like this which need to be kept in sync.

Matt Hughes

unread,
May 3, 2017, 10:14:03 PM5/3/17
to Elm Discuss
At first I wondered if a type is really what you need for this application, as opposed to say a list of strings. Anyway one idea is to write a function to explicitly convert the type to a string. I think this is better than simply taking the type name and making it a string because the string representation may need spaces or other punctuation. Using an explicit case means the compiler won't let you forget if you add a new type. 

module Main exposing (..)

import Html exposing (Html, text)

type Province = Alberta | BritishColumbia | Saskatchewan

toString : Province -> String
toString p =
    case p of
        Alberta -> "Alberta"
        BritishColumbia -> "British Columbia"
        Saskatchewan -> "Saskatchewan"

main : Html a
main =
    text (toString Alberta)

mch

Simon

unread,
May 6, 2017, 1:57:35 AM5/6/17
to Elm Discuss
I had a different use case for the same basic idea. With GraphQL we have a sort-of typed query language that returns clearly defined json structures. In an ideal world therefore you should be able to define an Elm data structure (sum or union type) and for library functions to be able to create the query string and the json decoder for you. I may be over optimistic, and such things may not even be possible in other ML-family languages, but it felt like something that should be automatable

Kasey Speakman

unread,
May 6, 2017, 11:47:48 AM5/6/17
to Elm Discuss
Going back to the original question, why couldn't you use a list of records instead of a union type? Every time I dealt with states, I ended up needing more than just the name and ended up putting it in a database, which more naturally maps to a record.

type alias UsState =
   
{ name : String
   
, code : String
   
, licenseFormat : String
   
}

With the usage you describe so far, records seem a better fit. Unions are more valuable when you need to structure to code against rather than data.

You could still pick out specific states for special rules using something like:

states
   
|> List.filter (\state -> state.code == "AL")
   
|> List.head

HTH

OvermindDL1

unread,
May 8, 2017, 10:51:41 AM5/8/17
to Elm Discuss
OCaml has a set of extension points.  You can write OCaml code, add it to an extension point (`derived` is precisely the use case of this) and it receives the AST of the expression and can transform it or add new code or whatever as you wish.  Like a very common one for auto-json conversion works like (example taken from docs):
```ocaml
type geo = {
  lat : float;
  lon : float;
}
[@@deriving yojson]
```
And this generates two functions in the module with the types of:
```ocaml
val geo_of_yojson : Yojson.Safe.json -> (ty, string) Result.result
val geo_to_yojson : ty -> Yojson.Safe.json
```
And they have a set of options so you can override the key names in the json or override the variant names or change the encoding of things like, oh, integers or specify a default value if it is missing and so forth.  Extension points like this in Elm would save on *so* much manually made code in so many cases...
Reply all
Reply to author
Forward
0 new messages