I want to specify a type for records that have an id field whose type is a Uuid. So, I'm using extensible records. The type looks like this:
type alias WithId a = { a | id : Uuid }
So far, so good. But then I went to create a Json.Encoder -- that is, a function of type WithId a -> Value -- for this type. I need a way to encode the underlying value, so I want to take a first argument of type a -> Value. Since I know a is a record, I can safely assume it is encoded to a JSON object, even though Elm doesn't expose the data types for Value.
However, when I create such a function, I get a compile error:
The argument to function `encoder` is causing a mismatch.
27| encoder a
^
Function `encoder` is expecting the argument to be:
a
But it is:
WithId a
Hint: Your type annotation uses type variable `a` which means any type of value
can flow through. Your code is saying it CANNOT be anything though! Maybe change
your type annotation to be more specific? Maybe the code has a problem? More at:
<https://github.com/elm-lang/elm-compiler/blob/0.18.0/hints/type-annotations.md>
Detected errors in 1 module.
I'm confused. Isn't something of type WithId a going to contain all the fields of a, and therefore shouldn't WithId a be an a through the type alias structural typing?
What am I missing? Why isn't the type alias of WithId a allowing me to use a WithId a as an instance of a, even though it is defined as {a|...}?
ADDENDUM
I've marked an answer below (which amounts to "You just can't do that, but here's what you're supposed to be doing."), but I'm still a bit unsatisfied. I guess I'm confused by the use of the term type alias. My understanding was that records were always a type alias, because they were explicitly specifying a subset of fields among all possible fields...but the underlying type was still the unifying record type (akin to a JS Object).
I guess I don't understand why we say:
type alias Foo = { bar : Int }
instead of
type Foo = { bar : Int }
The former implies to me that any record with { bar : Int } is a Foo, which would imply to me that {a|bar:Int} is both of the same type as a and a Foo. Where am I wrong here? I'm confused. I feel like I'm not grokking records.
ADDENDUM 2
My goal is not just to have a type WithId that specifies there is an .id on a field, but rather to have two types: Foo and WithId Foo where Foo has structure { bar:Int } and WithId Foo has structure { bar:Int, id:Uuid}. I'd then like to also have a WithId Baz, WithId Quux, etc., etc., with a single encoder and single decoder function for WithId a.
The basic issue is that I have persisted and non-persisted data, which have the exact same structure, except that once something is persisted, I have an id. And want I type-level guarantees that records are persisted in certain cases, so Maybe doesn't work.