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

Perhaps a bit of a different viewpoint, but one of the reasons that I keep coming back to Haskell is that it lets me refactor things things that I can't in other languages. Take the following C code:

    char* response(messy* message) {
      body* b = message->body;
      if(b==NULL) {
        return NULL;}
      char** lines = b->text;
      if(t==NULL) {
        return NULL;
      }
      if(b->line_count < 11) {
        return NULL;
      }
      return t[10];
    }
Writing all of those if statements grows tedious. A lazier programmer could just drop them, but then the program would crash whenever the program received malformed data.

With Hackell, though, I can refactor out the if statements and get the following equivalent code:

    response :: Messy -> Maybe String
    response message = do
      b <- body message
      lines <- text body
      lines `atMay` 10
I want to be clear on a couple of points. First, the Haskell code performs all of the same "null" and bounds checks that the C code made, so this is an apples-to-apples comparison. Furthermore, this isn't using exceptions, like a similar piece of C++ code might try. There's no stack unwinding and nothing can escape to contaminate the rest of your program. Finally, this isn't just a weird special case built into the language. With a minimal amount of effort, I could have each failure return a unique error Code.

    errorCode :: Maybe a -> b -> Either b a
    errorCode (Just value) _ = Right value
    errorCode Nothing code = Left code

    response :: Messy -> Either Int String
    response message = do
      b <- body message `errorCode` 1
      lines <- text body `errorCode` 2
      (lines `atMay` 10) `errorCode` 3
I don't know a way of doing that in any of the C style languages. In those languages, you're forced to add in another if statement every time your code touches message->body to ensure that you're not going to segfault. With Haskell, I can refactor away those if statements and I don't have that code being duplicated all throughout my project.

EDIT: improved readability of errorCode



Just a small side note, you don't even need to write your errorCode function. That and a number of other useful functions are provided in the errors package (http://hackage.haskell.org/package/errors). Also, I like to use string error messages because they're easier to grep for. Here's what your response function would look like with those changes.

    response :: Messy -> Either String String
    response message = do
      b <- note "error in body" $ body message
      lines <- note "error in text" $ text b
      note "error in atMay" $ lines `atMay` 10


For anyone reading this, I want to confirm that mightybyte's code would be far more idiomatic. I would never use integer error codes in a Haskell program, but I wanted to make the clear connection to C idioms. Also, using the errors package would be more appropriate than rolling your own function there, but I wanted to emphasize that it was simple, two line function, and not any fancy wizardry that requires caring about category theory.

EDIT: rechecking the errors library, the final line would probably be more clear with

      atNote "Not enough lines" lines 10


I've used a similar construct in dealing with some JSON data recently, using Either String a:

    (?) :: Maybe a -> String -> Either String a
    Nothing ? s = Left s
    Just a ? _ = Right a


    foo :: Value -> Either String Int
    foo val = do
        nested <- val ^? key "foo" . key "bar" ? "Could not find foo.bar"
        mid <- nested ^? nth 2 . _Integral ? "Second element of foo.bar was not an Integral value"
        return mid
which has really helped deal with the stringly typed nature of JSON. I should also mention I stole the idea (and operator name) from one of the Haskell parser combinator libraries which allowed you to name a whole parser expression, so errors would say "could not find ident" instead of "Expected on of a,b,c,d,e,d,f,g,h...., found X"


> Furthermore, this isn't using exceptions, like a similar piece of C++ code might try. There's no stack unwinding and nothing can escape to contaminate the rest of your program.

It always escapes to contaminate the rest of your program. In the C code, it escapes as a returned NULL. In C++/Java, it would be a thrown exception. In Haskell, it's the Maybe.

The Maybe is pretty close to isomorphic to a checked exception. The caller either has to handle it, or to return a Maybe/declare that it throws an exception itself. At some layer, you have to handle the Nothing/catch the exception. The Nothing short-circuits part of the computation; so does the exception. From where I sit, they're playing almost exactly the same roles. (So is the NULL in C, but it's clumsier than either an exception or a Maybe.)


You're right that the Maybe and Either types are very similar to checked exceptions. And, from that perspective, you're absolutely right that it contaminates the rest of the code around above it, similar to a checked exception. However, I would subjectively argue that Maybe is the less clumsy alternative. For the lazy programmer, the easy way to handle exceptions is to ignore them and let upstream deal with it. Even for checked exceptions, ignoring the problem is just a throws statement away. My subjective argument, though, is that monads are just slightly awkward enough to handle that you're encouraged to deal with the problem, instead of just ignoring it. I'd much rather handle whatever condition occurred than constantly deal with liftM and >>=, so I make sure to handle my "execeptions" as soon as I have enough information, just to make my own life easier. Your mileage may vary.


>I don't know a way of doing that in any of the C style languages

http://www.boost.org/doc/libs/1_49_0/libs/optional/doc/html/...

https://www.google.com/#q=maybe+in+c%2B%2B

...and also I couldn't resist...

    char* response(messy* message) {
        body* b = message->body;
        return b && (b->text) && b->linecount < 11 && b->text[10];
    }
...for a C-ish like language. And that's without creating a "do-notation" macro to erase the &&, as the Haskell do-notation erases the >>=.


These aren’t quite the same. With “x && x->y”, you’re using a boolean to encode the assumption that “x” is valid, but with “x >>= getY”, the type system has a proof, which makes refactoring safer.




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

Search: