Discussion:
[elm-discuss] List of all members of a union type
Matthew Buscemi
2017-05-03 02:39:11 UTC
Permalink
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
--
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.
Joey Eremondi
2017-05-03 03:56:17 UTC
Permalink
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.
Post by Matthew Buscemi
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
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
--
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.
Peter Damoc
2017-05-03 05:19:21 UTC
Permalink
Post by Joey Eremondi
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/
--
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.
Charles Scalfani
2017-05-03 14:14:22 UTC
Permalink
Not all types have default values. What's the default value of another
Union Type?
Post by Peter Damoc
Post by Joey Eremondi
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?
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/
--
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
2017-05-03 15:17:08 UTC
Permalink
Building on Charles, how would this handle recursive union types?
--
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.
Peter Damoc
2017-05-03 15:52:02 UTC
Permalink
Post by Max Goldstein
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.
--
There is NO FATE, we are the creators.
blog: http://damoc.ro/
--
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.
Charles Scalfani
2017-05-03 16:00:07 UTC
Permalink
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.
Post by Peter Damoc
Post by Max Goldstein
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.
--
There is NO FATE, we are the creators.
blog: http://damoc.ro/
--
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.
Peter Damoc
2017-05-03 16:12:16 UTC
Permalink
Post by Charles Scalfani
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.
--
There is NO FATE, we are the creators.
blog: http://damoc.ro/
--
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.
Ambrose Laing
2017-05-03 18:52:35 UTC
Permalink
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 <http://mbreen.com/m4.html#toc5> 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.
--
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.
Charles Scalfani
2017-05-03 15:34:58 UTC
Permalink
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).
Post by Matthew Buscemi
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
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
--
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.
Matt Hughes
2017-05-04 02:14:03 UTC
Permalink
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
Post by Matthew Buscemi
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
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
--
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.
Simon
2017-05-06 05:57:35 UTC
Permalink
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
Post by Matt Hughes
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
Post by Matthew Buscemi
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
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
--
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.
OvermindDL1
2017-05-08 14:51:41 UTC
Permalink
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...
Post by Simon
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
Post by Matt Hughes
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
Post by Matthew Buscemi
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
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
--
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.
Kasey Speakman
2017-05-06 15:47:48 UTC
Permalink
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
--
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...