Using Core_kernel.List.mem. And why usingBase&Core instead of stdlib?

Well, it’s right but I used it in the implementation of my universal search routines, and they looked just right there. So yeah for stuff like stopping a search. But also for events like a sub-program outputs an error. So, in those cases in the examachine project, this was tremendously helpful to be able to write readable code that you can come back to years later and still understand at first sight. I tried using option types too but all that pattern matching gets boring fast, too.

An extension that would break every single piece of OCaml code in existence? I’d rather invest my time into something that has at least some remote possibility of success.

I don’t think this is even possible with PPX unless the PPX is used to replace all exceptions with something else, but given that even C functions can thrown exceptions via FFI that is also not a workable solution.

I find option and result to be very readable with ppx_let. Without it, not so much, that’s true.

1 Like

With typed effects we will basically get the best of both worlds: the safety of options and the clarity of code and performance of exceptions. For now, if you want cleaner code, you can use an option monad. I don’t recommend exceptions in most cases because they forego typing, which is one of the big advantages of functional programming.

2 Likes

Typed effects are a proper generalization of event-driven programming and exceptions, it would be very cool to use them in everyday coding. :slight_smile:

Can you formally describe what are typed effects and what are their main advantages?
Maybe starting from event-driven programming to see the difference.

I understand that it’s not a feature included in OCaml yet. Is it about to be included soon?

Formally? No. But informally, if you take raising exceptions and tweak them so that you can go back to where you were at the time the exception was raised (ie. run the continuation), that’s an effect in this context. You can do any computation that can be done by monad with effects, and you’ll have cleaner code as well. This is still an untyped effect though, which will not show up in the type system, just as exceptions don’t.

If you now create a representation of the possible effects (again, think exceptions) that can be performed (raised) by each function and integrate it into the type system, you now have type-safe exceptions and type-safe effects (effects subsume exceptions, since exceptions just choose not to execute the continuation). This is extremely powerful. If you know haskell, imagine separating pure code from effectful code but without needing monads.

The untyped effect version was almost ready to go into OCaml together with multicore OCaml, but has wisely been pulled until the typed version is ready. Don’t expect it tomorrow, but it’s not as far away as say, modular implicits.

3 Likes

I can see (approximatively) 12 Predefined exceptions and 19 exceptions within 18 modules in OCaml 4.06.0

Which one do you think cannot be cleanly wrapped with some return types that will bring semantics?
And do you have an example, say in JS Core, of a function that has been redesigned to eliminate exception?


 == Predefined exceptions
exception Match_failure of (string * int * int)
exception Assert_failure of (string * int * int)
exception Invalid_argument of string
exception Failure of string
exception Not_found
exception Out_of_memory
exception Stack_overflow
exception Sys_error of string
exception End_of_file
exception Division_by_zero
exception Sys_blocked_io
exception Undefined_recursive_module of (string * int * int)

== Module exceptions
Pervasives exception Exit  
Arg        exception Help of string  
           exception Bad of string
Lazy       exception Undefined
Parsing    exception Parse_error
Queue      exception Empty
Scanf      exception Scan_failure of string
Stack      exception Empty
Stream     exception Failure
	   exception Error of string
Sys        exception Break
Location   exception Already_displayed_error
           exception Error of error
Pparse     exception Error of error
           exception Error of Location.t * Env.t * error
           exception Error_forward of Location.error
Unix       exception Unix_error of error * string * string
Graphics   exception Graphic_failure of string
Dynlink    exception Error of error
Details of Ocaml 4.06.0 exceptions

OCaml 4.06.0 exceptions

12 Predefined exceptions
23 exceptions within 18 modules

== Module Pervasives : The initially opened module.

val raise : exn -> 'a
val raise_notrace : exn -> 'a
val invalid_arg : string -> 'a
val failwith : string -> 'aval failwith : string -> 'a
exception Exit

val close_out : out_channel -> unit

Output functions raise a Sys_error exception when they are applied to a closed output channel, except close_out and flush, which do nothing when applied to an already closed channel. Note that close_out may raise Sys_error if the operating system signals an error when flushing or closing.

val input : in_channel -> bytes -> int -> int -> int

Exception Invalid_argument “input” is raised if pos and len do not designate a valid range of buf.

val close_in : in_channel -> unit

Input functions raise a Sys_error exception when they are applied
to a closed input channel, except close_in, which does nothing when applied to an already
closed channel.

val exit : int -> 'a

An implicit exit 2 is performed if the program terminates early
because of an uncaught exception.

val at_exit : (unit → unit) → unit
• terminates, either normally or because of an uncaught exception

== Module Arg : Parsing of command line arguments.
exception Help of string

Raised by Arg.parse_argv when the user asks for help.

exception Bad of string

Functions in spec or anon_fun can raise Arg.Bad with an error message to reject invalid
arguments. Arg.Bad is also raised by Arg.parse_argv[24.1] in case of an error.

== Module Callback : Registering OCaml values with the C runtime.

== Module Gc : Memory management control and statistics; finalised values.

== Module Genlex : A generic lexical analyzer.
A special character s is returned as Kwd s if s belongs to this list, and cause a lexical error (exception Stream.Error[24.39] with the offending lexeme as its parameter) otherwise.
A Stream.Failure[24.39] exception is raised if end of stream is unexpectedly reached.

== Module Lazy : Deferred computations.

exception Undefined
val force : 'a t -> 'a

force x forces the suspension x and returns its result. If x has already been forced,
Lazy.force x returns the same value again without recomputing it. If it raised an
exception, the same exception is raised again. Raise Lazy.Undefined[24.19] if the forcing of
x tries to force x itself recursively.

== Module Marshal : Marshaling of data structures.
val to_buffer : bytes -> int -> int -> 'a -> extern_flags list -> int

Marshal.to_buffer buff ofs len v flags marshals the value v, storing its byte
representation in the sequence buff, starting at index ofs, and writing at most len bytes.
It returns the number of bytes actually written to the sequence. If the byte representation
of v does not fit in len characters, the exception Failure is raised.

== Module Parsing : The run-time library for parsers generated by ocamlyacc.
exception Parse_error

Raised when a parser encounters a syntax error. Can also be raised from the action part of
a grammar rule, to initiate error recovery.

== Module Printexc : Facilities for printing exceptions and inspecting current call stack.
+++

== Module Queue : First-in first-out queues.
exception Empty

Raised when Queue.take[24.31] or Queue.peek[24.31] is applied to an empty queue.

== Module Scanf : Formatted input functions.

val from_function : (unit -> char) -> in_channel

Scanning.from_function f returns a Scanf.Scanning.in_channel[24.33] formatted
input channel with the given function as its reading method.
When scanning needs one more character, the given function is called.
When the function has no more character to provide, it must signal an end-of-input
condition by raising the exception End_of_file.

exception Scan_failure of string

When the input can not be read according to the format string specification, formatted
input functions typically raise exception Scan_failure.

Exceptions during scanning
Scanners may raise the following exceptions when the input cannot be read according to the format string:
• Raise Scanf.Scan_failure[24.33] if the input does not match the format.
• Raise Failure if a conversion to a number is not possible.
• Raise End_of_file if the end of input is encountered while some more characters are needed
to read the current conversion specification.
• Raise Invalid_argument if the format string is invalid.

== Module Stack : Last-in first-out stacks.
exception Empty

Raised when Stack.pop[24.37] or Stack.top[24.37] is applied to an empty stack.

== Module Stream : Streams and parsers.
exception Failure

Raised by parsers when none of the first components of the stream patterns is accepted.

exception Error of string

Raised by parsers when the first component of a stream pattern is accepted, but one of the
following components is rejected.

== Module Sys : System interface.
val signal : int -> signal_behavior -> signal_behavior

Set the behavior of the system on receipt of a given signal. The first argument is the signal
number. Return the behavior previously associated with the signal. If the signal number is
invalid (or not available on your system), an Invalid_argument exception is raised.

exception Break

Exception raised on interactive interrupt if Sys.catch_break[24.42] is on.

val catch_break : bool -> unit

catch_break governs whether interactive interrupt (ctrl-C) terminates the program or
raises the Break exception. Call catch_break true to enable raising Break, and
catch_break false to let the system terminate the program on user interrupt.

== Module Location : Source code locations (ranges of positions), used in parsetree.
exception Already_displayed_error
exception Error of error

== Module Pparse : Driver for the parser, external preprocessors and ast plugin hooks
exception Error of error
exception Error of Location.t * Env.t * error
exception Error_forward of Location.error

== Module Unix : Interface to the Unix system.
Note: all the functions of this module (except Unix.error_message[26.1] and Unix.handle_unix_error[26.1])
are liable to raise the Unix.Unix_error[26.1] exception whenever the underlying system call
signals an error.

exception Unix_error of error * string * string

== Module Graphics : Machine-independent graphics primitives.
exception Graphic_failure of string

Raised by the functions below when they encounter an error.

val make_image : color array array -> image

Convert the given color matrix to an image. Each sub-array represents one horizontal line.
All sub-arrays must have the same length; otherwise, exception Graphic_failure is raised.

== Module Dynlink : Dynamic loading of object files.
exception Error of error

Errors in dynamic linking are reported by raising the Error exception with a description of
the error.

Well as you can see there would be no trouble doing likewise with typed effects, the exceptions are already quite clear and in my opinion don’t even need much type marshaling to deal with if you know what you’re doing, but making things type safe in some way is appealing. I implore you to venture further in your thinking, do you see how well suited this stuff would be to implementing system code like a microkernel arch? Frankly, this might be one of the most exciting things to happen to OCaml, the (monadic etc.) glue code we wrote until now to deal with such stuff might look a bit kludgey in retrospect. Considering what set Erlang apart was some PL level support for distributed computation, this seems to support a wider, and safer programming paradigm. All of this is tangential thinking ofc, just trying to open up our imagination, a bit excited as a fanatical PL user :slight_smile:

I do not quite understand the question.

Yes, OCamls Map.find throws the Not_found exception by default which is a rather annoying exception because it does not even contain what was not found. On the other hand Core’s Map.find returns an option so your code has to specifically handle the case where there is no such key in the map. You’ll notice that both libraries contain the respective other variant, find_opt in the case of the OCaml stdlib and find_exn in Core, but the default behaviours are different and this is reflected throughout both standard libraries in many places (OCaml having _opt variants and Core having _exn variants).

1 Like

I very very strongly prefer the Core behavior, fwiw. I think most other people do at this point as well.

1 Like

Not necessarily. I find that often it’s more convenient for me to catch the exception in match with | exception Not_found …. Both behaviors are useful.

I totally agree that we must manage properly our code and must not product some dirty legacy. The question seems to be “How to do that?”

By “cleanly wrap (the exception) with some return types” I mean if the program gets a value v from a function then it returns Some v and if an exception is raised then it returns None.

Stdlib Map.Make provides:
val find_opt : key -> 'a t -> 'a option
that returns Some v or None
val find : key -> 'a t -> 'a
that returns the current binding or Not_found exception if it does not exist.

Core_kernel.Map provides:
val find : ('k, 'v, 'cmp) t ‑> 'k ‑> 'v Core_kernel__.Import.option
that returns Some v or None.
val find_exn : ('k, 'v, 'cmp) t ‑> 'k ‑> 'v
that returns the current binding or Not_found exception if it does not exist.

Core adds a cmp argument, switches the order of the Stdlib arguments that I’m used to and add a bit of Java-like verbosity within its signature (Core_kernel__.Import.option is 27 characters instead of 6). It looks like it intends to overload my brain with useless stuff…

So, in this example, what is the difference between using find_opt from Stdlib and find from Core_kernel? (or find and find_exn when handling an exception)
Maybe there is a difference of performance (10% 50% faster?). And where is it evoked?
The only difference I see is cmp. But what prevents me from using a monomorphic comparator? (if it is the issue)

I think theirs is just some affinity towards Scheme style coding and maybe the established wisdom that you shouldn’t use exception handling for better performance (esp. in C++) but these are mostly preferences because there isn’t a big performance hit for OCaml AFAICT. So the choice for me depends on which code would be shorter and more readable.

It’s neither an affinity towards a scheme coding style nor a performance issue. It’s that code that throws exceptions can fail because the type system doesn’t catch your mistakes. Nothing warns you of uncaught exceptions, nothing enforces that you catch them in the right place.

4 Likes

Let’s take the find function with exception (Map.Make.find or Core_kernel.Map.find_exn):

We know exactly where there is a risk to get the Not_found exception raised : where we call it.
And WE enforce ourselves to catch this possible exception. I agree that WE can be unaware of it but this reveals that WE don’t know stdlib/replacement lib functions which are basic building blocks.
The “_exn” in Core is a very good practice for warning us but it enforces nothing.
A simple check tool based on the list I extracted from 4.06 refman could warn us that some functions with possible exception are called without try/catch construct and that a runtime error can occur because of that.

Then, when we catch/handle exceptions as we must, this gives us the information we need: for find/find_exn, “There is no binding for the key put as argument”.

Did I miss something?

What if “we” are tired or not paying attention? The point of a safe language is that the compiler warns us of all errors. It is true that if you’re paying close attention you’re safe, but on the other hand, if you’re paying close attention, then you can write code in C that never faults, right?

Thanks, but no thanks. I want my compiler to tell me when I’ve made mistakes to the greatest extent feasible. If I wanted to program in a language where I had to do it myself, I know where to find them. There are literally hundreds to choose from.

4 Likes

The general case is not simple: catching an exception can happen anywhere in the call chain which is not statically known. The advantage of exceptions is precisely that you can raise them deep in a call hierarchy and handle them at a much higher level.

2 Likes

I agree with that. I would love that.
Is it a feature that could be simply added to the compiler?
Who knows why it hasn’t been included before?

BTW, a simple thought : if we make a bad decision from correctly catched exceptions or from return types (None/Some…), we won’t have a runtime error but our program won’t deliver what we expect.

On the other hand, we may also consider that struggling with a very type-safe system such as OCaml is somehow challenging, and keeps us aware about the things that live under the hood.

We already have a compiler that tells us about most errors, provided we aren’t using the exceptions mechanism overly frequently. I’m perfectly happy with types like option and result.

Exceptions are really bad from a type safety perspective. The whole point of a typechecked language is for the type checker to tell you when you’re missing something important. However, they’re more efficient in OCaml than allocating an option or result. For type safety, you have to use option or result. If you don’t want to use match on the option that’s returned, you can use monadic combinators to make it fairly painless. Eventually, we should have typed effects, which are like exceptions that can be tracked by the type system.

5 Likes