Guidance for using "type alias" vs. single-constructor union types

326 views
Skip to first unread message

Austin Bingham

unread,
Jan 16, 2017, 3:40:13 AM1/16/17
to Elm Discuss
I recently had to chase down a bug where I was calling a function with the arguments in the wrong order. The function's declared argument types were each an alias for string, so the compiler happily let me swap them. In order to avoid this in the future, I'm experimenting with using single-constructor union types in place of aliases, e.g. instead of "type alias Foo = String" I'm using "type Foo = Foo String". 

This seems to work well for helping me enforce the kind of type safety I'm looking for. However, it seems to force me to do a lot more de/restructuring of function arguments, and it feels a bit graceless. For example, a (contrived) function that was originally like this:

aFunc : Foo -> Foo
aFunc f = 
    let
        x = somethingWithAString f
    in
        somethingWithAFoo x f

becomes:

aFunc : Foo - > Foo
aFunc (Foo f) =
    let
        x = somethingWithAString f
    in
        -- Have to "repack" f into a Foo
        somethingWithAFoo x (Foo f)

That is, if I want to thread something of type "Foo" through calls while also using its data, I have to "repack" it.

So my question is: are there better ways to do this? Are there better patterns for a) adding the type-safety I want while b) avoiding the extra noise? I know some languages (e.g. clojure) let you destructure bindings *and* bind a name to the un-destructured thing. Something like that might help.

Any advice (including "you're doing it wrong!") would be appreciated.

Austin

Magnus Rundberget

unread,
Jan 16, 2017, 5:03:45 AM1/16/17
to Elm Discuss
Hi,

I haven't given it too much though, but I do think there is some very nice benefits to using single constructor union types.
In you case, you might avoid some of the noise by adding helper functions to work on values of the type (maybe you need a mapFoo helper function).
I found this blog post quite enlightening : https://robots.thoughtbot.com/lessons-learned-avoiding-primitives-in-elm

cheers
- magnus

Austin Bingham

unread,
Jan 16, 2017, 5:38:34 AM1/16/17
to Elm Discuss
Excellent article, thanks! If their experience reflects the state-of-the-art in Elm, then I guess I'm on the right track. I absolutely agree with a key sentiment of the article: I'm looking for something with the compiler-orientedness of Haskell's newtype. Ignoring the mostly superficial wart of needing to un/repacking my types, single-constructor unions get me what I need.

Ian Mackenzie

unread,
Jan 16, 2017, 8:49:46 AM1/16/17
to Elm Discuss
I agree with the point about adding helper functions for your data type so you can treat it as opaque in most places. For the rest, though, you can also use the slightly obscure 'as' syntax 'foo ((Foo s) as f) = ...' which allows you to use both 's' and 'f' in your function body (with the additional bonus of avoiding an extra object allocation).

Austin Bingham

unread,
Jan 16, 2017, 9:31:28 AM1/16/17
to Elm Discuss
Ah ha! That bit of syntax is exactly what I was looking for. That get's me a lot of what I was hoping for.

Regarding the use of helper functions, I agree in principle. But in my particular case, at least, I think it's mostly an academic issue. I want to distinguish between various "classes" of strings using the type system, but mostly all I do with them is store them and use them as keys. 

One interesting issue I've run into is the use of single-contructor unions as e.g. the key-type in a dict. As far as I can tell, I can't do something like this:

    type Foo = Foo String
    type alias FooDict = Dict Foo String

because Foo isn't "comparable" and can't be made so. Is there some way to do this, or is this just a limitation of elm?

art yerkes

unread,
Jan 16, 2017, 10:10:42 AM1/16/17
to Elm Discuss
You could try elm-generic-dict to have arbitrary keys in dicts.

Max Goldstein

unread,
Jan 16, 2017, 12:26:58 PM1/16/17
to Elm Discuss
It's tempting to want something like

type clone Name = String
type clone Currency = Int
type alias Accounts = Dict Name Currency

These hypothetical "type clones" would act like their base type until you tried to use them in nonsensical ways. But trying to define their behavior is tricky.

The Haskell community has apparently tried this under the name "subtypes" and concluded they are very difficult to implement and have other undesirable properties, but I couldn't find anything formal.

More concretely, say one defines type clones for Length and Width in the hope of avoiding accidentally adding them, which would make no sense. But we'd want to allow multiplying them to obtain Area. The only language I know that handles this well is Frink, which tracks physical units through all computations.

All that said, it may be possible to define this well enough and simply enough to warrant further explanation. For example, in any correct program with type clones, one should be able to replace any clone with an alias and have identical code generated. If there's interest, I can start a new thread exploring these type clones further.

Duane Johnson

unread,
Jan 16, 2017, 1:02:33 PM1/16/17
to elm-d...@googlegroups.com

On Mon, Jan 16, 2017 at 10:26 AM, Max Goldstein <maxgol...@gmail.com> wrote:
More concretely, say one defines type clones for Length and Width in the hope of avoiding accidentally adding them, which would make no sense. But we'd want to allow multiplying them to obtain Area. The only language I know that handles this well is Frink, which tracks physical units through all computations.

I believe F# also does this quite well:

Andrew Radford

unread,
Jan 16, 2017, 4:14:20 PM1/16/17
to Elm Discuss
+1, One of those awesome features in F# that does not seem to be that widely known. 

Max Goldstein

unread,
Jan 17, 2017, 1:48:00 AM1/17/17
to Elm Discuss
I did not know F# supported that, which is good news. It means that there's feasible, theoretically sound way of solving this problem. In Elm, it would require making the mathematical operations aware of units and therefore even more "special" that the number not-a-typeclass. But units still feel like the right abstraction for this problem, especially if strings could be marked as having a unit (never mix up usernames and passowrds again!), so I'd much rather consider adding units of measure to the language later than some weird, halfbaked idea now.

Bob Hutchison

unread,
Jan 23, 2017, 12:45:19 PM1/23/17
to Elm Discuss

On Jan 16, 2017, at 10:10 AM, art yerkes <art.y...@gmail.com> wrote:

You could try elm-generic-dict to have arbitrary keys in dicts.

Thank you! I didn’t know about this. This will remove a ton of cruft and ugly code from my code base. I use single-constructor ADTs all over the place, and they always seem to want to be keys to a dict (or set).

There’s a set implementation in the elm-generic-dict which is part two of the problem solved.
Reply all
Reply to author
Forward
0 new messages