# Enum helper package?

**URL:** <https://discourse.elm-lang.org/t/enum-helper-package/3426>\
**Category:** Request Feedback\
**Created:** [April 1, 2019, 9:22am UTC](https://discourse.elm-lang.org/t/enum-helper-package/3426 "2019-04-01T09:22:42Z")\
**Posts on this page:** 16\
**Page:** 1

<div class="post-metadata">

**Author:** ![Herteby](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/herteby/32/1329_2.png) [@Herteby](https://discourse.elm-lang.org/u/Herteby)\
**Post date:** [April 1, 2019, 9:22am UTC](https://discourse.elm-lang.org/t/enum-helper-package/3426/1 "2019-04-01T09:22:43Z")

</div>

I made this module to reduce boilerplate when dealing with enums.

To create an Enum, you first define your custom type:

```auto
type Fruit
    = Apple
    | Banana
    | Pineapple

```

And then you call `Enum.create` with a list of your variants, and a `toString` function:

```auto
enum : Enum Fruit
enum =
    Enum.create
        [ Apple
        , Banana
        , Pineapple
        ]
        (\a ->
            case a of
                Apple ->
                    "Apple"

                Banana ->
                    "Banana"

                Pineapple ->
                    "Pineapple"
        )

```

This gives you an `Enum a` which is a record containing common helper functions, as well as dict and list of the variants, which are useful for things like `<select>` elements.

```auto
type alias Enum a =
    { toString : a -> String
    , fromString : String -> Maybe a
    , encode : a -> Value
    , decoder : Decoder a
    , dict : Dict String a
    , list : List ( String, a )
    }

```

So you can then use these like `Fruit.enum.decoder` etc.

Curious if you think it’s worth releasing as a package, and what you think of this idea of returning a record of helper functions?

Currently it only supports strings, since that’s how our enums are encoded in JSON. If I were to release it I’d probably extend it to Ints as well.

Here’s the full code of the module, really short actually:

```auto
module Enum exposing (Enum, create)

import Dict exposing (Dict)
import Json.Decode as Decode exposing (Decoder, Value)
import Json.Encode as Encode

type alias Enum a =
    { toString : a -> String
    , fromString : String -> Maybe a
    , encode : a -> Value
    , decoder : Decoder a
    , dict : Dict String a
    , list : List ( String, a )
    }

create : List a -> (a -> String) -> Enum a
create list toStr =
    let
        list2 =
            list |> List.map (\a -> ( toStr a, a ))

        dict =
            Dict.fromList list2
    in
    { toString = toStr
    , fromString = \string -> Dict.get string dict
    , encode = toStr >> Encode.string
    , decoder =
        Decode.string
            |> Decode.andThen
                (\string ->
                    case Dict.get string dict of
                        Just a ->
                            Decode.succeed a

                        Nothing ->
                            Decode.fail ("Missing enum: " ++ string)
                )
    , dict = dict
    , list = list2
    }

```

---

<div class="post-metadata">

**Author:** ![gigobyte](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/gigobyte/32/1583_2.png) [@gigobyte](https://discourse.elm-lang.org/u/gigobyte)\
**Post date:** [April 1, 2019, 10:14am UTC](https://discourse.elm-lang.org/t/enum-helper-package/3426/2 "2019-04-01T10:14:18Z")

</div>

A package for that would be really useful! I played around with it and found that maybe a tuple of a string and a constructor is more compact than passing a toString function:

```
enum : Enum Fruit
enum =
    create
        [ ( "Apple", Apple )
        , ( "Banana", Banana )
        , ( "Pineapple", Pineapple )
        ]

```

There are some pros and cons to this approach, it optimizes for compactness but it prevents matching a wildcard, for example. What do you think?

---

<div class="post-metadata">

**Author:** ![Herteby](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/herteby/32/1329_2.png) [@Herteby](https://discourse.elm-lang.org/u/Herteby)\
**Post date:** [April 1, 2019, 10:22am UTC](https://discourse.elm-lang.org/t/enum-helper-package/3426/3 "2019-04-01T10:22:09Z")

</div>

It looks nicer, but the reason I didn’t go for that is because then `enum.toString` would kind of have to return a `Maybe String`.

Although maybe it would be acceptible to return a default string like `"Missing enum"` instead? 🤔 Because you would only encounter that situation if you forgot to add a variant to the list, which could still happen with the List + toString design.

So yeah maybe I will go with that actually!

---

<div class="post-metadata">

**Author:** ![gigobyte](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/gigobyte/32/1583_2.png) [@gigobyte](https://discourse.elm-lang.org/u/gigobyte)\
**Post date:** [April 1, 2019, 11:15am UTC](https://discourse.elm-lang.org/t/enum-helper-package/3426/4 "2019-04-01T11:15:33Z")

</div>

It will also be cool to have index-based decoding/encoding. I usually encode to strings because my Haskell back-end also works with ADTs, but I would imagine most traditional back-ends expect numbers instead of strings i.e. first member is 0, second is 1 and so on.

---

<div class="post-metadata">

**Author:** ![progger](https://avatars.discourse-cdn.com/v4/letter/p/5daacb/32.png) [@progger](https://discourse.elm-lang.org/u/progger)\
**Post date:** [April 2, 2019, 10:14pm UTC](https://discourse.elm-lang.org/t/enum-helper-package/3426/5 "2019-04-02T22:14:13Z")

</div>

I like the case based thing, because it gives a compile time error if all types aren’t accounted for - like if you add a new Fruit later, or remove one. For the same reason I’m more in favor or strings rather than index numbers for encoding, because its more future proof.

One thing that might be handy would be a function to make a list of all the enums. There was a thread about this a [while back](https://discourse.elm-lang.org/t/typesafe-url-parser-oneof-usage/1964). A function nextType could be used to recurse through all the types, and give a compile error if a case is missing, or a type was deleted.

---

<div class="post-metadata">

**Author:** ![gampleman](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/gampleman/32/43_2.png) [@gampleman](https://discourse.elm-lang.org/u/gampleman)\
**Post date:** [April 3, 2019, 9:37am UTC](https://discourse.elm-lang.org/t/enum-helper-package/3426/6 "2019-04-03T09:37:08Z")

</div>

It’s a bit of a shame that Elm 0.19 removed top level destructuring – this would have been very nice with that.

```elm
module Fruit exposing (Fruit(..), encode, decoder, toString, fromString)

import Enum

type Fruit
     = Apple 
      | Banana
      | Pineapple

-- doesn't work :(
{ encode, decoder, toString, fromString } =
    Enum.create
        [ Apple
        , Banana
        , Pineapple
        ]
        (\a ->
            case a of
                Apple ->
                    "Apple"

                Banana ->
                    "Banana"

                Pineapple ->
                    "Pineapple"
        )

```

I suppose you can still do:

```elm

module Fruit exposing (Fruit(..), encode, decoder, toString, fromString)

import Enum

type Fruit
     = Apple 
      | Banana
      | Pineapple

enum =
    Enum.create
        [ Apple
        , Banana
        , Pineapple
        ]
        (\a ->
            case a of
                Apple ->
                    "Apple"

                Banana ->
                    "Banana"

                Pineapple ->
                    "Pineapple"
        )

toString : Fruit -> String
toString = 
     enum.toString

-- etc

-- etc

```

---

<div class="post-metadata">

**Author:** ![Herteby](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/herteby/32/1329_2.png) [@Herteby](https://discourse.elm-lang.org/u/Herteby)\
**Post date:** [April 3, 2019, 10:04am UTC](https://discourse.elm-lang.org/t/enum-helper-package/3426/7 "2019-04-03T10:04:35Z")

</div>

@gampleman What I’ve been doing (since I’m already using this module myself) is something like:

```auto
module Order exposing (Fruit(..), fruit)

type alias Order =
    { id: Int
    , fruit: Fruit
    }

type Fruit
    = Apple
    | Banana
    | Pineapple

fruit : Enum Fruit
fruit =
    Enum.create
        [ ( "Apple", Apple )
        , ( "Banana", Banana )
        , ( "Pineapple", Pineapple )
        ]

```

And then I access the functions like `fruit.decoder`, so it’s like a psuedo-submodule 🙂

@progger Hmm, it’s not as readable though, and you could still “link” the variants in the chain incorrectly. I could include it as an alternative constructor though.

Btw. Something that would be pretty cool is if elm-test could get some magic introspection powers to fuzz test custom types! That could be the ultimate solution to this issue.

Wether you base them on Strings or Ints might depend on the backend API you’re using, so it’s still good to have Int as an option.

---

<div class="post-metadata">

**Author:** ![opvasger](https://avatars.discourse-cdn.com/v4/letter/o/8e8cbc/32.png) [@opvasger](https://discourse.elm-lang.org/u/opvasger)\
**Post date:** [April 3, 2019, 7:42pm UTC](https://discourse.elm-lang.org/t/enum-helper-package/3426/8 "2019-04-03T19:42:21Z")

</div>

The module looks nice ☀ I’m going to try this approach.

I’m currently doing [this](https://gist.github.com/opvasger/f6d5c9caad669107988a2447adac19a0), as I wanted the exhaustive type-check. I think having this approach as a separate constructor in your module would be nice 👌

---

<div class="post-metadata">

**Author:** ![rupert](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/rupert/32/1775_2.png) [@rupert](https://discourse.elm-lang.org/u/rupert)\
**Post date:** [April 3, 2019, 10:04pm UTC](https://discourse.elm-lang.org/t/enum-helper-package/3426/9 "2019-04-03T22:04:47Z")

</div>

> [@Herteby](#):
>
> Curious if you think it’s worth releasing as a package, and what you think of this idea of returning a record of helper functions?

Returning a record of function seems a bit odd, but why not if the type system allows it? I actually rather like it.

I tried checking the package site to see if I could find precedent for this approach, just randomly checking a few places I though I might have seen it before, but I could not find any. Would be interested to hear if there are other examples of this kind of thing.

---

<div class="post-metadata">

**Author:** ![gampleman](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/gampleman/32/43_2.png) [@gampleman](https://discourse.elm-lang.org/u/gampleman)\
**Post date:** [April 4, 2019, 12:03pm UTC](https://discourse.elm-lang.org/t/enum-helper-package/3426/10 "2019-04-04T12:03:35Z")

</div>

elm-css actually did something like that waaay back.

---

<div class="post-metadata">

**Author:** ![rupert](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/rupert/32/1775_2.png) [@rupert](https://discourse.elm-lang.org/u/rupert)\
**Post date:** [April 4, 2019, 12:51pm UTC](https://discourse.elm-lang.org/t/enum-helper-package/3426/11 "2019-04-04T12:51:47Z")

</div>

If you wanted to avoid returning a record of functions you could use an opaque type like this:

```
module Enum exposing (Enum, encode, decoder, toString, fromString)

type Enum a =
    Enum
        { toString : a -> String
        , fromString : String -> Maybe a
        , encode : a -> Value
        , decoder : Decoder a
        , dict : Dict String a
        , list : List ( String, a )
        }

toString : Enum a -> a -> String
toString helper val = 
    helper.toString val

fromString : Enum a -> String -> Maybe a
... and so on

```

Can’t really see this being nicer to use though.

---

<div class="post-metadata">

**Author:** ![Herteby](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/herteby/32/1329_2.png) [@Herteby](https://discourse.elm-lang.org/u/Herteby)\
**Post date:** [April 4, 2019, 3:33pm UTC](https://discourse.elm-lang.org/t/enum-helper-package/3426/12 "2019-04-04T15:33:41Z")

</div>

So here’s the first draft of the docs! 🚀  
[https://elm-doc-preview.netlify.com/Enum?repo=herteby%2Fenum&version=master](https://elm-doc-preview.netlify.com/Enum?repo=herteby%2Fenum&version=master)

And here’s the source: [https://github.com/Herteby/enum/blob/master/src/Enum.elm](https://github.com/Herteby/enum/blob/master/src/Enum.elm)

Would love some input!

Currently I have 4 different constructors for `EnumInt` which seems a bit much? 🤔 Which ones should I drop?

Also could someone please help tidy up the code for [iterate](https://github.com/Herteby/enum/blob/master/src/Enum.elm#L264)?  
I ran into an issue where the item which should be first in the list appeared last, and the code I wrote to fix it isn’t very pretty 😅

---

<div class="post-metadata">

**Author:** ![jreusch](https://avatars.discourse-cdn.com/v4/letter/j/b5e925/32.png) [@jreusch](https://discourse.elm-lang.org/u/jreusch)\
**Post date:** [April 4, 2019, 6:08pm UTC](https://discourse.elm-lang.org/t/enum-helper-package/3426/13 "2019-04-04T18:08:42Z")

</div>

I would drop the \*index versions of the `EnumInt` constructors. IMHO, it is generally a very bad idea to generate encodings based on the implicit enum index, since the indices could very easily change by accident, breaking the encoded data.

Implicit indices force you to make sure the enums are defined in exactly the same way on all sides, that there are no language-specific quirks happening, and that you never reorder or remove a value. [Protobuffer enums](https://developers.google.com/protocol-buffers/docs/proto#enum) for example force you to specify the values explicitely.

Here is a simplified version of the `iterate` function based on your `iterate2` function, and made tail-recursive:

```elm
iterate : (a -> (b, a)) -> a -> List ( b, a )
iterate iterator init =
    let
        helper : a -> List ( b, a ) -> List ( b, a )
        helper prevValue stack =
            let
                item =
                    iterator prevValue
                    
                value = 
                    Tuple.second item
            in
            if value == init then
                -- we get our starting value last, so we first reverse the list
                -- we built, and then add the starting value to the front.
                -- If the order is not actually important, we can also just
                -- return `item :: stack` here
                item :: (List.reverse stack)
                
            else
                helper value (item :: stack)
    in
    helper init []

```

---

<div class="post-metadata">

**Author:** ![Herteby](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/herteby/32/1329_2.png) [@Herteby](https://discourse.elm-lang.org/u/Herteby)\
**Post date:** [April 5, 2019, 8:03am UTC](https://discourse.elm-lang.org/t/enum-helper-package/3426/14 "2019-04-05T08:03:48Z")

</div>

@jreusch Yeah removing the implicit index constructors is probably best, I felt a bit uneasy about them too.

And thank you very much for the improved function! 🙂

I’m also thinking about removing `.dict`. Nothing wrong with it but I mostly just threw it in because I could. I think you’re far more likely to use `.list` for things like `<select>`, and you can ofc just use `Dict.fromList`.

---

<div class="post-metadata">

**Author:** ![Herteby](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/herteby/32/1329_2.png) [@Herteby](https://discourse.elm-lang.org/u/Herteby)\
**Post date:** [April 5, 2019, 11:28am UTC](https://discourse.elm-lang.org/t/enum-helper-package/3426/15 "2019-04-05T11:28:39Z")

</div>

1.0.0 released!

[https://package.elm-lang.org/packages/Herteby/enum/latest/](https://package.elm-lang.org/packages/Herteby/enum/latest/)

---

<div class="post-metadata">

**Author:** ![system](https://canada1.discourse-cdn.com/flex035/uploads/elm_lang/original/1X/50a05e53677a2c3b47776d7abd0f113eb50193a1.png) [@system](https://discourse.elm-lang.org/u/system)\
**Post date:** [April 15, 2019, 11:28am UTC](https://discourse.elm-lang.org/t/enum-helper-package/3426/16 "2019-04-15T11:28:40Z")

</div>

This topic was automatically closed 10 days after the last reply. New replies are no longer allowed.
