@jwoLondon, I am often pretty conservative about the union types that I expose.
I wrote about these “opaque types” a bit here, but here is the idea. Say you have a type like:
type Color = Red | Blue
And you are pretty sure there are other colors that you will add eventually. Rather than exposing the Red and Blue constructors directly, I would do this:
module Color exposing (Color, red, blue)
type Color = Red | Blue
red : Color
red =
Red
blue : Color
blue =
Blue
And now, I can add more stuff as minor changes!
Now the only difference here is that you cannot do pattern matches on Color anymore. In most cases, that is actually better. The particular way type Color works may change to something better or more efficient. Perhaps it becomes type Color = RGB Int Int Int once I realize that there are lots of colors people want. Well, I can implement red and blue with the new internal RGB constructor, and the public API stays exactly the same. Great!
But maybe you really want people to be able to pattern match. Why? Is that important to what your library is about? Is it the only way? Say that you answer all those questions and say that pattern matching is needed. Well, it should be a major version bump for your users because it is going to cause all of their pattern matches to fail.
So I only glanced at the link you shared, but my instinct is that there is not a strong reason to make the type constructors public for configuration kinds of things. (As an aside, I think doing configs in records, rather than lists that can have clashing settings, is a nice path if it is possible. I wonder if it is viable in some cases if the goal is to give nicer config than raw strings.)
Does that help?