[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [MirageOS-devel] Using Result instead of Option in libraries

On Mon, Oct 17, 2016 at 10:53 AM, Richard Mortier <richard.mortier@xxxxxxxxxxxx> 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



Lists.xenproject.org is hosted with RackSpace, monitoring our
servers 24x7x365 and backed by RackSpace's Fanatical Support®.