15

I have a type union of colors that I want to render to the user. Is it possible to iterate over all type union values?

type Color = Red | Blue | Green | Black

colorToStirng color = 
    case color of
        Red -> "red"
        Blue -> "blue"
        Green -> "green"
        Black -> "black"

colorList = 
    ul
        []
        List.map colorListItem Color  -- <- this is the missing puzzle

colorListItem color = 
    li [class "color-" ++ (colorToString color) ] [ text (colorToString color) ]
ondrej
  • 967
  • 7
  • 26

4 Answers4

16

The problem with declaring a function like:

type Foo 
  = Bar 
  | Baz

enumFoo = 
  [ Bar 
  , Baz ]

is that you will probably forget to add new enums to it. To solve this, I've been playing with this (hacky, but less hacky than the idea above) idea:

enumFoo : List Foo
enumFoo =
  let
    ignored thing =
      case thing of
          Bar ->  ()
          Baz -> ()
          -- add new instances to the list below!
  in [ Bar, Baz ]

This way you at least get an error for the function and hopefully don't forget to add it to the list.

Jezen Thomas
  • 13,619
  • 6
  • 53
  • 91
Greg Edwards
  • 598
  • 1
  • 5
  • 12
  • 3
    this is actually a smart workaround for such a limitation. Thanks! – pietro909 May 24 '17 at 15:36
  • Where does `thing` get defined? – JustGage Oct 10 '17 at 15:26
  • 2
    "ignored" is a function, so "thing" is the argument. The function is never called, but it tells the compiler to verify that all of the enumerations are listed in the case statement. It's completely separate from the array below -- "[ Bar, Baz ]" -- this approach is just a way to make a compiler error appear near the list, with a reminder for the programmer to add an entry to the array. – Greg Edwards Oct 11 '17 at 16:11
9

Unfortunately, no. It is not possible.

For a simple type with a finite number of values like your Color type it might seem like the compiler should be able to generate such a list. As far as the complier is concerned though, there is no difference between your type and a type like

type Thing = Thing String

To iterate over all the values of type Thing would then require iterating over all the values of type String.

robertjlooby
  • 7,160
  • 2
  • 33
  • 45
3

Oh course you can do it. Just not automatically via the compiler.

type Foo 
   = Bar 
   | Baz
   | Wiz

-- just write this for types 
-- you wish to use as enumerations 
enumFoo = 
   [ Bar 
   , Baz
   , Wiz ]

This works just fine, but obviously would be nicer and exhaustivity checked, if enumeration is ever supported by the compiler.

colorList = 
ul
    []
    List.map colorListItem enumFoo
Fresheyeball
  • 29,567
  • 20
  • 102
  • 164
1

This does not exactly answer your question, but since Elm is so similar to Haskell, I am probably not the only one wondering what the Haskell side of the story is.

So in Haskell (showing ghci) you can do:

Prelude> data Color = Red | Blue | Green | Black deriving (Show, Bounded, Enum)
Prelude> [minBound..maxBound]::[Color]
[Red,Blue,Green,Black]

Maybe a future version of elm will borrow from that.

Edit: I found Getting a list of all possible data type values in Haskell which also answers this.

hkBst
  • 2,818
  • 10
  • 29