On Mon, Oct 17, 2016 at 10:53 AM, Richard Mortier <
richard.mort...@cl.cam.ac.uk> wrote:

> What was your experience here? Why *would* one force a closed type?

First, a terminology correction. What I was calling a "closed" variant is
actually an "exact" variant. A "closed" variant is of the form [< ... ].
Regarding what I do, the point is that an exact, open, and closed variant
all say different things, so I try to annotate my values with the type that
matches my intention, which is usually an exact variant. Sometimes I can't
get my code to compile then, e.g. an exact variant in combination with a
private type, so then I do whatever necessary to make the code compile. Not
a helpful answer; maybe someone knows the theory better and can give a
clearer explanation of what works when.

> Now you have to manually handle the errors every time x and f are used in
> > the same context. Should some support for that be standardized?
> Is this got around by naming types per your suggestion below?

No. This is inherently something the compiler can't help you with. You've
got 2 incompatible error types. You have to manually say how to merge them
to a single error type.

> > calling out to additional code in a function's implementation doesn't
> change
> > that function's return type.
> That's true, but perhaps not unexpected given the error possibilities
> are explicitly being exposed in the type?

Right, you're getting exactly what you asked for.

> Now you're in a situation where return types
> > change all the time.
> "all the time" sounds a bit over-stated :) How often in practice did
> this sort of thing happen?

Good point. The frequency at which your types change is really the main
issue. In my experience, it was unbearably often. This was a couple of
years ago, and all of my repos were in lots of flux. As your APIs
stabilize, your types change less often. So perhaps the Mirage project
switching to this style now is the correct timing.

However, I think polymorphic variant error types don't stabilize as easily.
Somehow what counts as an error and what exact information you might want
from an error type is less clear than in the Ok case. The Ok case drives
your functionality. The Error case doesn't. Also, in the Ok case, the
library author usually knows what to provide. In the Error case, often the
library's user needs to specify what they need. This reversal leads to more
code churn.

> > Quickly you'll have functions that say they can return error `Foo but
> > actually they cannot.
> That's true. Can't think of a useful way round that one.
> (But is any alternative better?)

I can't think of any sane solution. Maybe some tooling to automatically
erase constructors one at a time and see if your code still type checks,
but that's getting absurd.

It seems error handling is an unsolved problem. The tension is that errors
are something you usually want to ignore, but when you do want to handle
them, you want to do so with a high level of precision. (Could algebraic
effects help?)

Here's one idea that could work now. Define precise error types within each
of your modules, and provide de/serialization functions from/to string.
Pass around the string type as your error type, avoiding all the problems
mentioned. When some client code wants to handle the error more precisely,
they can call your deserializer. The client has to know which errors they
want to handle, so they know which deserializers to call. The client can't
discover the full list of possible errors so you lose completeness. Maybe
that's okay; anyway there's no such thing as handling *all* errors. There
is a performance penalty for the serialization/deserialization.

> val with_file :
> >      string ->
> >      f : (t -> (‘a, [> err] as ‘b) Result.t ->
> >      (‘a, ‘b) Result.t
> Well, I at least would be interested in knowing if you've time!
> Off-list if you prefer or it would bore people... :)

I'll try but would welcome a better explanation from the experts. For
clarity, let's define

type err = [ `File_doesnt_exist | `File_is_dir ]

The intention is:

- The implementation of with_file needs to open the file with the given
name. This could lead to errors of type err.
- The user will provide some function f operating on the opened file, which
could lead to have some errors of type 'b.
- Thus, the overall result can have errors of their union, [err | 'b].

But what you end up having to write includes err in f's return type. This
seems odd because the point was that err is all the errors that can happen
with the rest of the code, everything except what f does. However, there's
no reason f couldn't also return err values, in addition to some others,
which is exactly what the correct version of the code says. Not allowing
this possibility would mean we want to insist that f's return type excludes
any value of type err, and I guess that's not expressible in the type
system (Or is it? However, even if it is, that's not really what we
originally meant, so the above solution is I think the correct one).
MirageOS-devel mailing list

Reply via email to