#+Title: Haskell Web Application from scratch #+Author: Yann Esposito * Introduction ** Functional Programming oriented to make a web application ** Tooling choices - macOS Sierra - spacemacs - stack (not using ghc-8.0.1, there is a bug with macOS) ** High quality application Even if an application only print "hello world" there are a lot of subtle way such an app could fail or have problems. See for example the [changelogs to GNU Hello](https://github.com/avar/gnu-hello/blob/master/ChangeLog). The goal of this tutorial is not to provide a "see what we can do with Haskell" but more, how could we enforce production quality development with Haskell. Unfortunately, the tooling is very important in these matters. To reach such goal we should at least provide: - Documentation - Unit Tests - Generative Tests - Benchmarks - Profiling - CI - Deployment It's easy to have one tutorial for each of these concepts, here that won't be a deep dive, but a first introduction on how to achieve all these goals. * Tooling and preparing to code blog-image("stillsuit.jpg","Stillsuit") ** Warning If you never installed Haskell before, it should be a bit long to setup a correct working environment. So please follow me, don't give up because something doesn't work the first time. I made my best to make my environment work for most people. ** Installing Haskell Compiler Install Haskell etc... In my opinion the easiest way to start is to install =stack=. Then you need to choose a great name for your project, why not =shai-hulud=? blog-image("shai-hulud.jpg","Shai Hulud") #+BEGIN_SRC bash > stack new shai-hulud tasty-travis #+END_SRC Yeah now you have a new directory, let use git: #+BEGIN_SRC bash > cd shai-hulud > git init . #+END_SRC Now we have some source code, let's try it[^1]. [^1]: If you are on a Mac, please, modify the line =resolver: lts-7.18= by =resolver: nightly-2017-01-25= to be certain to use =ghc-8.0.2= because there is a bug with =ghc-8.0.1=. #+BEGIN_SRC bash > stack setup && stack build && stack exec -- shai-hulud-exe 42 #+END_SRC ** Dependencies & Libraries As we want to make a web application let's add the needed dependencies to help us. Typically we want a web server [warp](https://hackage.haskell.org/package/warp) and also a Web Application Interface [WAI](https://hackage.haskell.org/package/wai). We'll also need to use [http-types](https://hackage.haskell.org/package/http-types). In the =shai-hulud.cabal= file, in the =shai-hulud-exe= section, add to the build-depends =http-types=, =wai= and =warp= like this: #+BEGIN_SRC executable shai-hulud-exe default-language: Haskell2010 ghc-options: -Wall -Werror -O2 -threaded -rtsopts -with-rtsopts=-N hs-source-dirs: src-exe main-is: Main.hs build-depends: base >= 4.8 && < 5 , shai-hulud , http-types , wai , warp #+END_SRC Then we modify the =src-exe/Main.hs= file to contains: #+BEGIN_SRC haskell {-# LANGUAGE OverloadedStrings #-} import Network.Wai import Network.HTTP.Types import Network.Wai.Handler.Warp (run) app :: Application app _ respond = do putStrLn "I've done some IO here" respond $ responseLBS status200 [("Content-Type","text/plain")] "Hello, Web!" main :: IO () main = do putStrLn "http://localhost:8080/" run 8080 app #+END_SRC We'll go in detail later about what everything means. #+BEGIN_SRC bash > stack build && stack exec -- shai-hulud-exe ... ... Lot of building logs there ... http://localhost:8080/ #+END_SRC Yeah! It appears to work, now let's try it by going on in a web browser. You should see =Hello, Web!= in your browser and each time you reload the page a new message is printed in the console because some IO were performed. ** So can we start yet? Hmmm no sorry, not yet. We should not use the default prelude. While this article is a tutorial, it is not exactly a "very basic" one. I mean, once finished the environment would be good enough for production. There will be tests, ability to reproduce the build on a CI, and so, for such a program I should prevent it to have runtime errors. In fact, certainly one of the main reason to use Haskell is that it helps prevent runtime errors. In order to do that we'll use a prelude that doesn't contain any partial functions. So I choosed to use =protolude=[^2]. For that that's quite easy, simply add =protolude= as a dependency to your cabal file. We'll modify the cabal file that way: #+BEGIN_SRC library default-language: Haskell2010 ghc-options: -Wall -Werror -O2 hs-source-dirs: src exposed-modules: {-# higlight #-}Lib{-# /highlight #-} , ShaiHulud.App build-depends: base >= 4.8 && < 5 , http-types , protolude , wai executable shai-hulud-exe default-language: Haskell2010 ghc-options: -Wall -Werror -O2 -threaded -rtsopts -with-rtsopts=-N hs-source-dirs: src-exe main-is: Main.hs build-depends: shai-hulud , base >= 4.8 && < 5 , http-types , protolude , wai , warp #+END_SRC We move the =app= declaration in =src/ShaiHulud/App.hs=: #+BEGIN_SRC haskell {-# LANGUAGE NoImplicitPrelude #-} {-# LANGUAGE OverloadedStrings #-} module ShaiHulud.App ( app ) where import Protolude import Network.Wai import Network.HTTP.Types app :: Application app _ respond = do putText "I've done some IO here" respond $ responseLBS status200 [("Content-Type","text/plain")] "Hello, Web!" #+END_SRC And we remove it from =src-exe/Main.hs=: #+BEGIN_SRC haskell {-# LANGUAGE NoImplicitPrelude #-} {-# LANGUAGE OverloadedStrings #-} import Protolude import Network.Wai.Handler.Warp (run) import ShaiHulud.App (app) main :: IO () main = do putText "http://localhost:8080/" run 8080 app #+END_SRC So now the tooling around being able to start working seems done. ** Not yet Yes I talked about: - Installation with =stack= that should take care of installing Haskell - How to add dependencies by adding them to the cabal file - Sane prelude with =protolude= - Provided an overview of WAI Application type But I forgot to mention part of the tooling that is generally very personal. I use spacemacs and to take advantages of many of the editor niceties I also use =intero= and =haddock=. So other things to think about: - Install =intero= with =stack install intero=. - Also generate hoogle documentation: =stack hoogle data= - You could also check the tests and benchmark suites: =stack test= and =stack bench= ** So we should be done with prelimiaries So we should be done with preliminaries, at least, I hope so... If you started from scratch it was certainly a terrible first experience. But be assured that once done, most of the step you've taken won't be needed for your next project. * Web Application So what is a web application? ** WAI So if you look again at the code you see that your application main function simply print =http://localhost:8080/= and then run the server on the port =8080= using =app=. The type of =app= is =Application=, if we look at the type of Application in WAI, for example by using =SPC-h-h= on the Application keyword or by going in the [WAI documentation](https://www.stackage.org/haddock/lts-7.18/wai-3.2.1.1/Network-Wai.html). We see that: #+BEGIN_SRC haskell type Application = Request -> (Response -> IO ResponseReceived) -> IO ResponseReceived #+END_SRC Hmmmm.... What? So just remakr WAI is at it's third major version. So if we just take a look at WAI in its previous version we see that Application was defined as: #+BEGIN_SRC haskell type Application = Request -> IO Response #+END_SRC Which look quite more intuitive. Because, what is the role of a web server if not sending response to requests? The IO here is just there to explain that in order to send a response the server might use IOs like reading in some DB or the file system. So why let's take a bit to analyze the new definition of =Application= in WAI 3. #+BEGIN_SRC haskell type Application = Request -> (Response -> IO ResponseReceived) -> IO ResponseReceived #+END_SRC It is explained: #+BEGIN_QUOTE The WAI application. Note that, since WAI 3.0, this type is structured in continuation passing style to allow for proper safe resource handling. This was handled in the past via other means (e.g., ResourceT). As a demonstration: #+BEGIN_SRC haskell app :: Application app req respond = bracket_ (putStrLn "Allocating scarce resource") (putStrLn "Cleaning up") (respond $ responseLBS status200 [] "Hello World") #+END_SRC #+END_QUOTE Great, so before it was difficult to handling some resources, now it appears to be easier to write using =bracket_=. Hmm... =bracket_=? What is this function? If you search it in [hoogle](https://www.haskell.org/hoogle/?hoogle=bracket_): OK that's quite easy, you see it is a function of =Control.Exception.Base= that we could use like this: #+BEGIN_SRC haskell bracket (openFile "filename" ReadMode) (hClose) (\fileHandle -> do { ... }) #+END_SRC And =bracket_= is a variation of =bracket= which doesn't need the return value of the first computation to be used the the "closing" computation. (More details here)[http://hackage.haskell.org/package/base-4.9.1.0/docs/Control-Exception-Base.html#v:bracket_]. So ok, an =Application= is "mostly" a function that take a =Request= an returns an =IO Response=. Good, now let's take another look to the =app= code: #+BEGIN_SRC haskell app :: Application app _ respond = do putText "I've done some IO here" respond $ responseLBS status200 [("Content-Type","text/plain")] "Hello, Web!" #+END_SRC As you see we don't use the first parameter, the =Request=. So we could ask for some JSON data on =/foo/bar/= with a POST, it will still respond an HTTP 200 with content-type plain text containing the body =Hello, Web!=. So what a web app should provide. And here we could go down the rabbit hole of the HTTP standard and all its subtleties. But the first thing to come in mind is "how to handle routing"? One of the advantage of using a language with some types flexibility is to use the types as a high level specification. ** Routing #+BEGIN_SRC haskell data ShaiHuludApp = Routes -> Application #+END_SRC That's easy, provided a "Routes" representation we should be able to "generate" a WAI Application. Now how should we represent a set of =Routes=? We should split them by: - HTTP Verb: =GET=, =POST=, =PUT=, =DELETE=, =HEAD=, =OPTIONS=, ... - Path: =/=, =/users/:userID= ... - Content-Type: =application/json=, =text/plain=, =text/html=, =text/css=, =application/javascript=... Hmmm.... So it is immediately very difficult. And these are just the basic requirement, what about all subtelties about Standardized Headers (CORS, ETags, ...), Custom Headers... ** Is that FP compatible? As a functional programmer, and more generally as a scientis, math lover I immediately dislike that profusion of details with a lot of ambiguities. For example, REST is still ambiguous, should you use POST / PUT to update? Should you put a parameter in: - part of the path like =/user/foo= - in the query param of the URI =/user?userID=foo= - in the body? Then what parser should we use? FORM param, JSON, XML? - in the headers? - Why not as an anchor? =/user#foo - How should I provide a parameter that is a list? A set? A Hash-map? Something more complex? The problem of web programming come from the tooling. Browsers and HTTP evolved together and some feature existed in browser before people had really the time to think corectly about them. That's called real-life-production-world. And it sucks! For a better critique of web standards you should really read [the chapter «A Long digression into how standards are made» in Dive into HTML5](http://diveintohtml5.info/past.html#history-of-the-img-element). So how could we get back our laws, our composability? Our maths and proofs? We have a lot of choices, but unfortunately, all the tooling evolved around the existing standards. So for example, using GET will be cached correctly while POST won't. And a lot of these details. *** FP Compatible Web Programming? Let's re-invent web programming with all we know today. First, one recent trends has changed a lot of things. Now a web application is splitted between a frontend and backend development. The frontend development is about writing a complete application in a browser. Not just a webpage. The difference between the two notions is blurred. Once consequence is that now, backend application should only present Web API and should never send rendering informations. Only datas. So this is a simplification, the backend should simply expose "procedures", the only things to think about are the size of the parameter to send and the size of the response. As every of these objects will go through the wire. But there are interresting rules: - =GET= for read only functions - =POST= generic read/write functions - =PUT= idempotent read/write functions - =DELETE= like =PUT= but can delete things But there are also HTTP code with so many different semantics. - =1xx=: technical detail - =2xx=: Successful - =3xx=: Redirection - =4xx=: Client Error - =5xx=: Server Error So there are some similarities with the HTTP 1.1 reference and the control on functions we try to achieve with Haskell. One thing I'd like to imagine is simply that a Web API should simply be like a library. We could simplify everything _a lot_ by removig most accidental complexity. If we consider a web application to be split between a frontend application and a backend application it changes a lot of things. For example, we could mostly get rid of urls, we can consider to use the backend as a way to expose procedures. Let's for example decide to use only POST, and send parameters only in the body. In Haskell we could write: #+BEGIN_SRC haskell foo :: IO X -- ⇒ POST on /foo bar :: A -> IO X -- ⇒ POST on /foo with a body containing an A #+END_SRC And that's it. * Appendix ** Haskell Fragmentation vs Di There are many other prelude, one of my personal problem with Haskell is fragmentation. Someone could see "diversity" another one "fragmentation". Diversity is perceived as positive while fragmentation isn't. So is diversity imply necessarily fragmentation? Could we cure fragmentation by making it hard for people to compete? I don't think so. I believe we could have the best of both world. Then fragmentation occurs. And fragmentation is bad, because if you have an issue with your choice, the number of people that could help you is by nature reduced. I would say that there is fragmentation when there is no one obvious choice. But having an obvious choice for a library for example doesn't really prevent diversity. Fragmentation: - NixOS, raw cabal + Linux, stack - preludes - editor - stream library - orientation of the language "entreprisy ready, production oriented" make it work being dirty, add dirty choices for research people working in the language, "research oriented" make it beautiful or don't make it, block entreprisy people. ** =bracket_= [^3]: Also if you are curious and look at its implementation it's quite short and at least for me, easy to inuit. #+BEGIN_SRC haskell bracket :: IO a -- ^ computation to run first (\"acquire resource\") -> (a -> IO b) -- ^ computation to run last (\"release resource\") -> (a -> IO c) -- ^ computation to run in-between -> IO c -- returns the value from the in-between computation bracket before after thing = mask $ \restore -> do a <- before r <- restore (thing a) `onException` after a _ <- after a return r bracket_ :: IO a -> IO b -> IO c -> IO c bracket_ before after thing = bracket before (const after) (const thing) #+END_SRC Very nice