Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

    Even the best functional programmers I've seen fall
    back to some kind of exit-or-exception semantic in
    the None case.
I offer two responses to this. I'm going to use Jane Street's alternative Core standard library for OCaml (http://janestreet.github.io/) in my examples, because that's what I'm most familiar with.

First, consciously raising an exception is far, far better than having an exception raised without you being aware of it. I can search OCaml code for all of the instances of raise, failwith, and _exn, and end up with a reasonable understanding of when my code might conceivably fail. I can't do the same thing in Java, because many of the functions I'm calling might return null.

And it's really not that hard to deal with optional values when you have them. Sure, it's slightly more verbose than not having to deal with null, but in practice you have to write a bunch of if statements checking if things are null everywhere anyway.

Also, most languages don't have good abstractions for dealing with nulls despite them being everywhere, so they're always painful. Languages like OCaml and Haskell do have good abstractions, so you can often avoid a lot of the verbosity: if you have an optional value and really do want to raise an exception (usually only in scripts/smaller programs), wrap your value in Option.value_exn. If you have a reasonable default value, call Option.value ~default:(...). If you have a bunch of optional values together, you can sequence them together with Option.(>>=), because Option is a monad.

Second, there are alternatives to raising exceptions in the middle of big programs. Consider the Or_error library (see https://github.com/janestreet/core/blob/master/lib/or_error....), which is a part of Core. Rather than having a type like Option everywhere:

    type 'a option = Some 'a | None
It also has types like Or_error (defined approximately like this):

    type 'a or_error = Ok 'a | Error Error.t
Error.t is a very useful type that lets you lazily construct error messages from values, so that you don't pay the (often high) cost of serializing things into a human-readable form until you actually decide you need the error message.

What this lets you do is write your code inside the Or_error monad. Because it's a monad, it's very easy to sequence error-generating operations together, and you don't have to check for errors unless you explicitly decide you want to.

Often, you can do something useful like log the error to a log file or display an alert, rather than killing off the entire program. Sometimes, of course, an error is bad enough that it should cause the program to die, and then you can make it terminate with Or_error.ok_exn. But that's relatively rare. If you're trying to write robust systems, many errors are things you can deal with intelligently, and having those errors encoded in the type system ensures you don't forget to do it.



thanks for this reply, it's super informative! I've done a fair amount of ocaml programming but not as much as I would like with Core. Or_error looks really nice.

I think I underestimate the benefits that come with reducing the surprise in code. I get a similar amount of distaste lots of matching on option types and throwing to comparing to null and throwing. or_error definitely would help with that distaste I think.

you still have to give a lot of consideration to exactly what the error means, I think. in a lot of programs I write, the presence of an error cascades a lot and doing things like unwinding transactions and ensuring that the fault leaves a component in a consistent state is much more difficult than identifying all of the locations in a component where a failure could arise. maybe I'm doing something much higher level incorrectly but I've read a lot of other peoples code and it either has 10x the code to deal with, for example, many potential null-return scenarios or "works most of the time" and presents extremely critical consistency problems if a "stars align" error occurs ...


Yep, this is a hard problem. I wasn't super enthusiastic about the verbosity of option types when I first started programming OCaml, but almost two years later I'm quite convinced that they're the right way to do things. I really don't think there's a whole lot of downside, and being able to basically just not think about a large and hard-to-identify class of bugs is incredibly freeing.

This may not cover your use cases, but for cascading errors you can often write code something like the following:

     match
       foo_err ()
       >>=? bar_err
       >>=? baz_err
       [... etc ...]
     with
     | Error e -> Log.error e
     | Ok result ->
       [...]
This ends up working pretty well when you're working with pure functions because there's not anything to unwind, but you're right that it doesn't solve all problems.

If you're interested in talking to more people about this kind of thing, the Core mailing list might be a good place to try: https://groups.google.com/forum/?fromgroups#!forum/ocaml-cor...


I'd just like to add that I definitely think I've seen the light and believe that this is the way forward, I just don't think that having nullable values is a dealbreaker when it comes to memory safety, because even if you don't have nullable values you still have huge problems to deal with in your code to make your system robust.

exception safety, maybe, but that's a superset of memory safety?




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: