I'm reading Real World Ocaml and i have problems understanding how to present detailed yet uncluttered error messages to the user. I really like the idea of error-aware return types but getting the desired result out of Core seems to be impossible. I am probably doing something wrong. The examples in the book that i had seen so far look similar to the following one in a sense that they detect an exception and report "unexpected failure" or some such.
(* part 1 *)
(* Execute the DuckDuckGo search *)
let get_definition ~server word =
try_with (fun () ->
Cohttp_async.Client.get (query_uri ~server word)
>>= fun (_, body) ->
Pipe.to_list body
>>| fun strings ->
(word, get_definition_from_json (String.concat strings)))
>>| function
| Ok (word,result) -> (word, Ok result)
| Error _ -> (word, Error "Unexpected failure")
I am trying to design a somewhat better user experience for a command line application. The purpose of the application is to connect to a web service, fetch some JSON data, parse it and save the result. So when the connection fails, i would like to print some useful information as to why but no more information than absolutely necessary.
The following little program tries to connect to a non-existing hostname.
utop # #require “uri”;;
utop # #require “uri.top”;;
utop # #require "async";;
utop # open Async.Std;;
utop # #require "cohttp.async";;
utop # let uri = Uri.of_string "http://nosuchhost.local";;
val uri : Uri.t = http://nosuchhost.local
utop # let result = try_with (fun () -> Cohttp_async.Client.get uri);;
val result : (Cohttp.Response.t * string Pipe.Reader.t, exn) Result.t Deferred.t = <abstr>
utop # result >>| ident;;
- : (Cohttp.Response.t * string Pipe.Reader.t, exn) Result.t = Core.Std.Result.Error (lib/monitor.ml.Error_
((exn
(lib/core_unix.ml.Inet_addr.Get_inet_addr nosuchhost.local
"host not found”))
(backtrace (""))
(monitor
(((name try_with) (here ()) (id 5) (has_seen_error true)
(someone_is_listening true) (kill_index 0))
((name try_with) (here ()) (id 2) (has_seen_error true)
(someone_is_listening true) (kill_index 0))
((name main) (here ()) (id 1) (has_seen_error false)
(someone_is_listening false) (kill_index 0))))))
So there is a value of an error-aware return type which currently contains an exception.
First problem – i do not know what possible exceptions a function like Cohttp_async.Client.get can raise. In cases where i checked this was not a part of documentation. Is there any way short of reading the source code?
With the help of the OCaml mailing list, i managed to extract the following information
utop # result >>| (fun x -> match x with Error exn -> Exn.to_string @@ Monitor.extract_exn exn | Ok _ -> "ok");;
- : string = "(lib/core_unix.ml.Inet_addr.Get_inet_addr nosuchhost.local \"host not found\")"
But this is not quite what i want because i cannot display this to the user. What i want to display is something along the lines of
Fatal error: "nosuchhost.local" host not found
or maybe (in case of a network problem) just something along the lines of "Network connection is down"
Currently i do not see how i can do that other than by grepping the returned error string, which sounds ugly. And i don't even know what exceptions can be raised.
With my limited understanding of OCaml and Core i see two extremes – one is throwing the entire sexp at the user, another one is the way taken by the RWO book which is to present "unexpected failure" message. The latter in spirit is along the lines of an Eclipse error box which i had seen in my java days. The box was saying something like "null pointer exception while displaying null pointer exception".
Am i doing it wrong? How do i design robust functions which have user-friendly error reporting?
@avsm @yminsky – please please help if you can, i'm somewhat reluctant to post this to the mailing list – i feel as if i am asking too many questions already...