With that, "aeson" for JSON, "sqlite-simple" for persistence and perhaps something like "lucid" for HTML, you are half-way to that twitter clone... :)
[1] http://stackoverflow.com/questions/23727768/which-parts-of-r...
http://www.amazon.com/Beginning-Haskell-A-Project-Based-Appr...
The main advantage for me is in code reuse and refactoring, something that really shines for medium-to-large projects, and is unfortunately hard to showcase in tutorial style.
Getting started wasn’t easy, but as you imply, not really due to the language—mainly because the learning materials didn’t suit me. I couldn’t bring myself to finish Learn You a Haskell or any other tutorial, so I just used the language to build stuff and figured things out.
Been thinking about writing a book about my experiences, but it’s Yet Another Project I’ll have to make the time for…
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.
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 >>=.For me, using yesod has been quite a revelation. All of the errors in db operations get caught during my cabal build. This link https://github.com/yesodweb/yesod/wiki/ghci is something i used to test my db code all within the repl cycle after setting some pg env variables. All in all, its way smoother than many other environments i have worked with.
How about a URL shortener instead of a Twitter clone?
https://github.com/ryantrinkle/memoise
Video here: https://vimeo.com/59109358
Pandoc (http://pandoc.org/) is written in Haskell and its modular design makes it easy to peruse without understanding everything.
Haskell shines the brightest when refactoring a moderately sized project (I have been yearning for this, working in a smallish Python codebase), but nothing prevents using it for small things.