1. error handling should be based on exceptions or the result monad defined in core.
2. if the precondition is admittedly easy to check before calling the function, raise a generalist exception like Failure, Assertion_failed or Invalid_argument.
3. on the contrary if verifying the precondition of the function is roughly as difficult as what the function does, provide two versions of the function
- foo : u -> (v, e) Result.t, where e is a type describing the error
- foo_exn : u -> v that raises an exception E of e
4. the type e and the exception E should be defined in a module M.Error
wiki page [1], which presents result monad as the default error handling mechanism in biocaml. To be honest, I'm not confortable with this position, ... biocaml is geared towards a community where people very seldom know of functional programming
Hi Philippe. Thanks for getting this discussion started. My comments below.1. error handling should be based on exceptions or the result monad defined in core.I agree.2. if the precondition is admittedly easy to check before calling the function, raise a generalist exception like Failure, Assertion_failed or Invalid_argument.I think you're saying in this case we should have only an exception-ful version. Why should it differ from 3, where you recommend foo returns Result and foo_exn throws an exception.
Also, what is the argument for using a general exception? I think you're right, but let's state the reasoning. I think it is: if you're raising an exception, you already decided not to care about error handling (else, assuming you except my previous point, you would have used the version returning Result). Thus, there's little point in defining a special exception. Just make the error string informative.
3. on the contrary if verifying the precondition of the function is roughly as difficult as what the function does, provide two versions of the function
- foo : u -> (v, e) Result.t, where e is a type describing the error
- foo_exn : u -> v that raises an exception E of eI agree.4. the type e and the exception E should be defined in a module M.ErrorI've spent quite a lot of time now playing with different error types in large code bases. I'm really converging on using Or_error. This seems the best compromise, and was promoted as the good choice by Jane Street recently:
Using stronger types, like polymorphic variants, is very cumbersome. I tried it for months. I had fun, but I'm getting tired of it. Most programmers would run scared from it.
Minor point: I don't think exceptions have to be defined in a sub-module M.Error. They can just go directly in M. If we also decided Or_error is the way to go, then we don't have any special error types, and thus M.Error goes away completely.
wiki page [1], which presents result monad as the default error handling mechanism in biocaml. To be honest, I'm not confortable with this position, ... biocaml is geared towards a community where people very seldom know of functional programmingI agree. I hope to be proven wrong, but I doubt most bioinformaticians will ever learn what a monad is.
It's rather easy to provide both versions of a function, so I don't see any problem supporting both beginners and experienced FP programmers. I'm happy with our current solution of providing foo and foo_exn, and it is easy enough to provide both versions.
result
type is the default, client code (within Biocaml and externally) will essentially have to be monadic." and I really think we should not keep this position. To be even more of an extremist, I'd argue that result types are useful *only* when there are used scarcely enough so that you don't have to resort to monadic notation. Because then, they are a great tool to stress places where a detailed error handling is necessary (cf the prototypical Map.find example). --
You received this message because you are subscribed to the Google Groups "biocaml" group.
To unsubscribe from this group and stop receiving emails from it, send an email to biocaml+u...@googlegroups.com.
To post to this group, send email to bio...@googlegroups.com.
Visit this group at http://groups.google.com/group/biocaml.
To view this discussion on the web visit https://groups.google.com/d/msgid/biocaml/CAMu2m2Jng8Am%3DxHsLGB1xy3Ec5i8XucR1nxubCeqnTUE9Tgj6A%40mail.gmail.com.
For more options, visit https://groups.google.com/d/optout.
To view this discussion on the web visit https://groups.google.com/d/msgid/biocaml/CAOOOohQCTKWrKJixMLkhGh2AL%3DmFmV6fExcruJaaTUWxQ%3DTfrg%40mail.gmail.com.
I think we can separate out these two issues:
A) Whether to raise exceptions or return Result types.B) What error information should be carried by either the exception or Result type.
Your argument about A) is that the Result monad doesn't provide any enhanced flow control over exceptions. In both cases, you mostly ignore the error and can look at it if you want to. I mostly agree with this, but one difference is that the Result type gives you compiler enforced documentation about which functions return errors. I think that's the only difference; do you agree or have I missed something? If so, the question is how important is that?
The second item B) is orthogonal to the decision on A). With either exceptions or Result types, we can use plain strings or highly precise types. Highly precise types are more difficult to think of and maintain, thus slowing down development. Strings are not amenable to matching, so keep us worried about safety. I think there's a middle ground that gives us a simple type and safety; Core's Error.t. When you're being lazy and want to rapidly prototype some code, you can make an Error.t from a plain string. When you want more precise information, you can easily construct an Error.t from a complex data value (by using sexp). If you need to handle the error, you can deserialize the sexp and get back your structured value.
And apparently, constructing Error.t's is more efficient than constructing strings outright because your strings are constructed lazily, only if the error is ever looked at.Your point about raising exceptions if the error can easily be checked has finally sunk in. Thanks for explaining it again. I agree with this. So many functions can raise an error, and we really don't to rename all of them to _exn. But then, for consistency, why not reverse our naming convention. How about removing the _exn suffix and instead add _res versions on functions that return Result.
This would be much nicer for demos and newcomers. We really don't want to demo a script that has _exn all over the place, and, though a trivial issue, this kind of syntactic dirtiness is what keeps less experienced programmers away from otherwise good languages.
To view this discussion on the web visit https://groups.google.com/d/msgid/biocaml/CAMu2m2KgHoiOjgTYA2AuMS%2B1cVie1OuExxhQckq9qObaKXzkyQ%40mail.gmail.com.
This makes Error.t a lot more appealing to me! One question then: when you want to deserialize a complex data value, how do you know which deserializer you should apply?
For the result variant, how about just priming it?
val parse : string -> t
val parse' : string -> t Or_error.t
--
You received this message because you are subscribed to the Google Groups "biocaml" group.
To unsubscribe from this group and stop receiving emails from it, send an email to biocaml+u...@googlegroups.com.
To post to this group, send email to bio...@googlegroups.com.
Visit this group at http://groups.google.com/group/biocaml.
To view this discussion on the web visit https://groups.google.com/d/msgid/biocaml/CAMu2m2LTxCZ8UzVcupoQdf5f%3DS4d84rs1cyqPnMqL1CAR9vsjA%40mail.gmail.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/biocaml/CALScVYkR7eAsc9LmrMPJCTpCxBHn7Z59zR45Fa0Ki7mcwRLPQA%40mail.gmail.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/biocaml/CAOOOohST4RpV4a3PNd-Htu0gvVX_mzTzXCg4itzwukkjG4eCgg%40mail.gmail.com.
On Mon, May 26, 2014 at 12:19 PM, Philippe Veber <philipp...@gmail.com> wrote:
This makes Error.t a lot more appealing to me! One question then: when you want to deserialize a complex data value, how do you know which deserializer you should apply?
You don't, so it isn't as safe as a rich type from the onset. However, I think there are easy ways to alleviate this. For example, we could follow a standard that any Error.t's created in module Foo include the string "Foo" as their first component (or something like that). We could even define a strong type but always serialize it to Error.t before returning it. Then you've almost got what we have now without crazy type signatures.
as soon as I want to get back the rich error type value the Sexp hidden in the Error, I have to do it without safety net, that is without compiler guarantee, right? So to me this approach looses something important wrt to the rich type style: now all errors are basically represented as strings, and accessing to more structured representations can be considered unsafe in a certain sense.
One big argument in favor of Result.t was refactoring: if an error changes, you have the compiler complaining in all places where your code should be modified.
- style 1/2a is nice for detailed error handling but leads to clumsy code when you chain several possibly failing functions. Alternatively, if you carefully choose your types then they can unify but then you fall in the dreadful polymorphic variant hell, with lengthy/unreadable error types
- style 2c is very relaxing when chaining many possibly failing functions, but prevents a satisfying access to structured error types
type parsing_error = string * int
exception Parsing_error of parsing_error
val parser_res : string -> (t,parsing_error) Result.t
val parser_exn : string -> t (* raises Parsing_error _ *)
val parser_err : string -> t Or_error.t
--
You received this message because you are subscribed to the Google Groups "biocaml" group.
To unsubscribe from this group and stop receiving emails from it, send an email to biocaml+u...@googlegroups.com.
To post to this group, send email to bio...@googlegroups.com.
Visit this group at http://groups.google.com/group/biocaml.
To view this discussion on the web visit https://groups.google.com/d/msgid/biocaml/CAOOOohTPdW3if%2BLbSQXMt2dikwSDw%2BQvmn_Y5uQ5VohAjz2KOA%40mail.gmail.com.
IIUC, you are proposing to also functorize over the error type 'err within ('ok, 'err) Result.t. Is that right?
If yes, can you describe the argument to such a functor for the Fastq module.
> I am unconfortable with dropping the style carrying maximum information and safetyIn what sense does using Error.t reduce safety?
(I insist we say Error.t, not Or_error.t, otherwise there is a misunderstanding about what I'm proposing.)
You lose exhaustivity, but I see no other loss. And I don't think exhaustivity is so critical in error handling,
at least not enough that it worth using a hugely more complicated error type.
To view this discussion on the web visit https://groups.google.com/d/msgid/biocaml/CAMu2m2LjGBgFfHSf8eefhnMjF%2B4xGOXCmmZQHiA5AZ--J%3Dmk3g%40mail.gmail.com.
If yes, can you describe the argument to such a functor for the Fastq module.
module Biocaml_fastq : sig
type parsing_error = ...
type t
module Make(F : Future.S) : sig
val parse : string -> (t, parsing_error) Result.t
end
module MakeCP(F : Future.S)(E : sig val string_of_parsing_error : parsing_error -> string end) : sig
val parse : string -> t Or_error.t Result.t
end
end
Let's say you have a code like that:
let result_table fn =
Fastq.parse fn >>= fun fq ->
f fq >>= fun x ->
g x >>= function
| Ok y -> html_table y
| Error e -> Error.sexp_of_t e |> handle_sexp
handle_sexp is supposed to handle errors from Fastq.parse, f and g so that it can produce an html element in any case. If you happen to add an intermediate step in the function
let result_table fn =
Fastq.parse fn >>= fun fq ->
f fq >>= fun x ->
g x >>= fun z ->
h z >>= function
| Ok y -> html_table y
| Error e -> Error.sexp_of_t e |> handle_sexp
The compiler will not warn you that handle_sexp won't do a good job with errors from h, you have to think about it yourself.
Here is another situation. Let's say we have
val f1 : string -> t Or_error.t
val f2 : string -> t Or_error.t
where the error hides something of type t.
Suppose now we realize that f1 and f2 should have different errors. We should now modify the error handlers in the codebase. We start separating t into t1 and t2 to have the compiler complain, but for each error handler it shows you, you'll have to make a choice between the two sexp converters. If you choose the wrong one, the compiler won't complain here.
You lose exhaustivity, but I see no other loss. And I don't think exhaustivity is so critical in error handling,If it was not, why would exceptions be considered a worse solution than Result.t [1]?
at least not enough that it worth using a hugely more complicated error type.I thought the complicated error types were a problem if you have to combine them all along in monadic style. Except from that, do you think they are still more a burden than helpful?
I should add that while I liked the idea of having a couple of APIs each geared towards a particular context, I am not strongly opposed to establishing Or_error.t as the default error signaling mechanism and keeping error types simple.
--
You received this message because you are subscribed to the Google Groups "biocaml" group.
To unsubscribe from this group and stop receiving emails from it, send an email to biocaml+u...@googlegroups.com.
To post to this group, send email to bio...@googlegroups.com.
Visit this group at http://groups.google.com/group/biocaml.
To view this discussion on the web visit https://groups.google.com/d/msgid/biocaml/CAMu2m2%2BYdC1tp8ZTNUFw4-JVER%2Bi_0usHz357tVPPwBF-kqKkw%40mail.gmail.com.