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
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` 10EDIT: rechecking the errors library, the final line would probably be more clear with
atNote "Not enough lines" lines 10 (?) :: 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"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.)
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 >>=.The compiler won't let you generate an executable if you missed a code location.
Whereas in dynamic languages you never know if you have covered all the code locations.