diff --git a/tutorials/09_elm-intro.md b/tutorials/09_elm-intro.md new file mode 100644 index 0000000..f1cb312 --- /dev/null +++ b/tutorials/09_elm-intro.md @@ -0,0 +1,924 @@ +# Elm - Introduction + +## Installation, Editors and Plugins + +To install Elm, follow the official [installation guide](https://guide.elm-lang.org/install.html). There is also a section about how to configure different editors to work with Elm. + +Make sure to install [elm-format](https://github.com/avh4/elm-format) to your editor as well. + +There is also an online live editor for Elm called [Ellie](https://ellie-app.com). + + +## What is Elm + +According to the official website [elm-lang.org](https://elm-lang.org): + +> A delightful language for reliable webapps. +> +> Generate JavaScript with great performance and no runtime exceptions. + +It is a reactive pure functional programming language with syntax inspired by Haskell that compiles to JavaScript. It is designed for building reliable web applications with no runtime exceptions. Elm is one of the solutions for [the JavaScript problem](https://wiki.haskell.org/The_JavaScript_Problem). The [compiler](https://github.com/elm/compiler) is implemented in Haskell. + + +Elm is language **and** a framework for building front-end web applications. + +### Advantages + +- static strong type system, no `null` or `undefined` (static code analysis, when it compiles, it works) +- pure functions (no side effects, allows tree shaking on a function level) +- everything necessary for building front end apps is already included in the language +- standard ways how to do things, so it is easy to understand other people's code +- easy refactoring + +### Disadvantages + +- need for learning a new language +- sometimes, more code is needed than it would be in JavaScript (e.g., when parsing JSONs) +- lightweight Haskell (e.g., no type classes, which can result in more boilerplate code) +- harder to find developers for Elm projects than for JavaScript projects. + +### Compared to JavaScript + +Elm has built-in common tools and features that are part of typical JavaScript stack. + +| JavaScript | Elm| +| --- | --- | +| npm/yarn | built-in | +| Webpack | built-in | +| React | built-in | +| Redux | built-in | +| TypeScript/Flow | built-in | +| Immutable.JS | built-in | + + +## Alternatives + +### [Haste](https://haste-lang.org) + +Haste is a tool for compiling Haskell code into a JavaScript code and a server-side binary. Both programs can talk to each other ensuring type-safe communication. + +### [GHCJS](https://github.com/ghcjs/ghcjs) + +GHCJS is a compiler for Haskell to JavaScript that uses GHC API. It supports a wide range of Haskell features, including all type system extensions supported by GHC. There are some interesting frameworks for building web applications based on GHCJS: + +- [Reflex](https://reflex-frp.org) - A composable, cross-platform functional reactive programming framework for Haskell. +- [miso](https://haskell-miso.org) - A tasty Haskell front-end framework. + +### [PureScript](http://www.purescript.org) + +PureScript is a strongly-typed functional programming language that compiles to JavaScript. It is similar to Haskell with [some differences](https://github.com/purescript/documentation/blob/master/language/Differences-from-Haskell.md). It has a lot of packages published in [Pursuit](https://pursuit.purescript.org). Also, some frameworks for building web applications are there, e.g.: + +- [Thermite](https://github.com/paf31/purescript-thermite) - A simple PureScript wrapper for React. +- [Halogen](https://github.com/slamdata/purescript-halogen) - A declarative, type-safe UI library for PureScript. +- [Pux](https://github.com/alexmingoia/purescript-pux) - A library for building type-safe web applications. + +If you are interested, you can have a look at this comparison: [Benchmarks: GHCJS (Reflex, Miso) & Purescript (Pux, Thermite, Halogen)](https://medium.com/@saurabhnanda/benchmarks-fp-languages-libraries-for-front-end-development-a11af0542f7e) + +### [ReasonML](https://reasonml.github.io) + +Reason is a new syntax and toolchain based on OCaml programing language created by Facebook. The syntax is closer to JavaScript than OCaml. It is intended for development of front-end web applications and compiles to JavaScript. Using existing JavaScript and OCaml packages is possible. + + +## Elm REPL + +If you have Elm installed, you can run Elm REPL by `elm repl` command. It is like `ghci` for Elm. + +``` +$ elm repl +---- Elm 0.19.0 ---------------------------------------------------------------- +Read to learn more: exit, help, imports, etc. +-------------------------------------------------------------------------------- +> +``` + + +## Elm Language + +### Basic types + +Strings are enclosed in double quotation mark `"`. We use `++` operator to join them. + +#### String + +```elm +> "Hello world!" +"Hello world!" : String + +> "Hello " ++ "world!" +"Hello world!" : String +``` + +#### Numbers + +Elm has two number types `Int` and `Float` and constrained type variable `number` which can be either `Int` or `Float`. + +```elm +> 5 + 5 +10 : number + +> 5 + 5 * 3 +20 : number + +> (5 + 5) * 3 +30 : number + +> 2 ^ 8 +256 : number + +> 2 / 3 +0.6666666666666666 : Float + +> 7 // 2 +3 : Int + +> modBy 7 2 +2 : Int +``` + +#### Bool and logical operators + +Elm has standard operators for comparison and boolean operations. Same as in Haskell, it uses `/=` operator for inequality. + +```elm +> 5 > 7 +False : Bool + +> (5 == 7) +False : Bool + +> 5 /= 7 +True : Bool + +> not (5 /= 7) +False : Bool + +> False || True +True : Bool + +> False && True +False : Bool + +> not False && True +True : Bool +``` + +### Naming things + +If we want to give a name to expression, we use the `=` operator. + + +```elm +> x = 5 +5 : number + +> x +5 : number +``` + +### Function + +The definition and function call looks the same as in Haskell. + +```elm +> linear a b x = a * x + b + : number -> number -> number -> number + +> linear 5 3 7 +38 : number +``` + +### If expression + +```elm +> if True then "It is true!" else "It is not true." +"It is true!" : String +``` + + +### Comments + +Elm has multiline and single-line comments. + +```elm +{- + a multiline comment +-} + +-- a single line comment + +``` + + +### The Elm project + +The easiest way to initialize an Elm project is to use `elm init` command. + +``` +$ elm init +Hello! Elm projects always start with an elm.json file. I can create them! + +Now you may be wondering, what will be in this file? How do I add Elm files to +my project? How do I see it in the browser? How will my code grow? Do I need +more directories? What about tests? Etc. + +Check out for all the answers! + +Knowing all that, would you like me to create an elm.json file now? [Y/n]: y +Okay, I created it. Now read that link! + +$ ls +elm.json src +``` + + +It generates `elm.json` file that defines where the source files are and what are the project dependencies. + +```json +{ + "type": "application", + "source-directories": [ + "src" + ], + "elm-version": "0.19.0", + "dependencies": { + "direct": { + "elm/browser": "1.0.1", + "elm/core": "1.0.2", + "elm/html": "1.0.0" + }, + "indirect": { + "elm/json": "1.1.3", + "elm/time": "1.0.0", + "elm/url": "1.0.0", + "elm/virtual-dom": "1.0.2" + } + }, + "test-dependencies": { + "direct": {}, + "indirect": {} + } +} +``` + +### Modules + +Now, when we have our project ready, we can create some modules. A module has a name which should be the same as the file name. It has to state what expression from the module should be exposed explicitly. + + +```elm +-- src/Lib.elm + +module Lib exposing (linear) + + +linear a b x = + a * x + b +``` + +We can import the module to other modules or the repl using `import` statement. If we use just `import Lib` we need to use the full name for the expressions from the module. + +```elm +> import Lib +> Lib.linear 5 3 7 +38 : Int +``` + +We can also expose some expression and then use them without the full module name. + +```elm +> import Lib exposing (linear) +> linear 5 3 7 +38 : Int +``` + +Or we can expose everything from the module. + +```elm +> import Lib exposing (..) +> linear 5 3 7 +38 : Int +``` + +Or we can import a module with a different name. + +```elm +> import Lib as Math +> Math.linear 5 3 7 +38 : Int +``` + +### Type annotation + +Elm can infer the types based on what we are doing. However, it is a good practice to define the types. + +```elm +linear : Float -> Float -> Float -> Float +linear a b x = + a * x + b +``` + + +### Type variables + +When the specific type is not important, we can use a type variable. Type names start with an uppercase letter (e.g., `Float`), type variable can be almost any string starting with a lowercase letter. + +```elm +> List.isEmpty + : List a -> Bool + +> List.map + : (a -> b) -> List a -> List b + +``` + +#### Constrained Type Variables + +There are several constrained type variables with a special meaning defined by the Elm language. + +- `number` permits `Int` and `Float` +- `appendable` permits `String` and `List a` +- `comparable` permits `Int`, `Float`, `Char`, `String`, and lists/tuples of `comparable` values +- `compappend` permits `String` and `List comparable` + + +```elm +linear : number -> number -> number -> number +linear a b x = + a * x + b +``` + +### List + +A list is a collection of items of the same type with variable length. There is a [List](https://package.elm-lang.org/packages/elm/core/latest/List) module with various functions for working with lists. + +```elm +> numbers = [1, 3, 5, 7, 11] +[1,3,5,7,11] : List number + +> List.length numbers +5 : Int + +> List.isEmpty numbers +False : Bool + +> double n = n * 2 + : number -> number +> List.map double numbers +[2,6,10,14,22] : List number + +> List.map (\n -> n * 2) numbers +[2,6,10,14,22] : List number + + +> List.map ((*) 2) numbers +[2,6,10,14,22] : List number +``` + + +### Dict + +Dict is a mapping of unique keys to values. There is a [Dict](https://package.elm-lang.org/packages/elm-lang/core/latest/Dict) module with functions for working with dicts. + +```elm +> Dict.fromList [("Spencer", 25), ("Zoe", 21)] +Dict.fromList [("Spencer",25),("Zoe",21)] : Dict.Dict String number + +> Dict.insert "Spencer" 25 Dict.empty +Dict.fromList [("Spencer",25)] : Dict.Dict String number + +> dict = Dict.fromList [("Spencer", 25), ("Zoe", 21)] +Dict.fromList [("Spencer",25),("Zoe",21)] : Dict.Dict String number +> Dict.isEmpty dict +False : Bool +> Dict.get "Zoe" dict +Just 21 : Maybe number +> Dict.get "Frankie" dict +Nothing : Maybe number +``` + +### Tuple + +A tuple is a collection of items of various type with the fixed size. There is a [Tuple](https://package.elm-lang.org/packages/elm/core/latest/Tuple) module for working with tuples. Tuples can be used when a function returns more than one value. + + +```elm +> person = ("Joe", 21) +("Joe",21) : ( String, number ) + +> Tuple.first person +"Joe" : String + +> Tuple.second person +21 : number +``` + +We can use pattern matching for tuples in functions: + +```elm +bio : (String, Int) -> String +bio (name, age) = name ++ " is " ++ (String.fromInt age) ++ " years old." +``` + + +Elm has a limit on the maximum number of items in the tuple to be 3. If you need more, you should use a record or your own custom type. + +``` +> vector4 = (4, 10, 12, 3) +-- BAD TUPLE --------------------------------------------------------------- elm + +I only accept tuples with two or three items. This has too many: + +8| vector4 = (4, 10, 12, 3) + ^^^^^^^^^^^^^^ +I recommend switching to records. Each item will be named, and you can use the +`point.x` syntax to access them. + +Note: Read for more comprehensive advice on +working with large chunks of data in Elm. +``` + + +### Record + +Records contain keys and values. Each value can have a different type. + +```elm +> vector4 = { w = 4, x = 10, y = 12, z = 3 } +{ w = 4, x = 10, y = 12, z = 3 } + : { w : number, x : number1, y : number2, z : number3 } +``` + + +For accessing record properties, Elm has by default accessors defined as `.`. They can be used as `.`, but it is just syntactic sugar, they are just functions. + +```elm +> vector4.x +10 : number + +> .x vector4 +10 : number + +> List.map .x [vector4, vector4, vector4] +[10,10,10] : List number +``` + +If we have a look at the type of `.x` accessor, it says it is any record that has a field `x` of type `a` and returns `a`. + +```elm +> .x + : { b | x : a } -> a +``` + + +Since everything is immutable, records cannot be updated. We can create updated records though: + +```elm +> { vector4 | x = 20 } +{ w = 4, x = 20, y = 12, z = 3 } + : { w : number1, x : number, y : number2, z : number3 } +``` + + +We can use pattern matching for record keys: + +```elm +> length {w, x, y, z} = sqrt (w * w + x * x + y * y + w * w) + : { b | w : Float, x : Float, y : Float, z : a } -> Float +> length vector4 +16.61324772583615 : Float +``` + +### Type alias + +Type aliases are used to give a new name to existing types. It is useful for naming record types. + + +```elm +type alias Name = + String + + +type alias Age = + Int + + +type alias Person = + { name : Name + , age : Age + } + + +isAdult : Person -> Bool +isAdult { age } = + age >= 18 +``` + +```elm +> import Lib exposing (..) + +> joe = { name = "Joe", age = 21 } +{ age = 21, name = "Joe" } + : { age : number, name : String } + +> isAdult joe +True : Bool + +> joe = Person "Joe" 21 +{ age = 21, name = "Joe" } : Person +``` + +*Note*: Type aliases are resolved in compiled time. Therefore, they cannot be recursive. For recursion, we need to use custom types. + + +### Custom Types + +We can define custom types that have several variants. We can also associate data with a variant. + + +```elm +type Gender + = Male + | Female + + +type Tree a + = Leaf a + | Branch (Tree a) (Tree a) + + +type Profile + = Loading + | Error String + | Success Person + + + +gender = Female + +tree = Branch (Leaf 1) (Branch (Leaf 2) (Leaf 0)) + +profile = Error "Cannot load profile" + +``` + +### Pattern Matching + +```elm +isFemale : Gender -> Bool +isFemale gender = + case gender of + Male -> + False + + Female -> + True +``` + +We can use wildcard `_` for all other branches in the `case` statement or for the variables we don't need. + +```elm +isLoading : Profile -> Bool +isLoading profile = + case profile of + Loading -> + True + + _ -> + False + + +profileStatus : Profile -> String +profileStatus profile = + case profile of + Loading -> + "Loading" + + Error error -> + "Error: " ++ error + + Success _ -> + "Success!" + +``` + +We can use `::` operator for matching first element and rest of the list. + +```elm +sum : List number -> number +sum list = + case list of + head :: tail -> + head + sum tail + + [] -> + 0 +``` + + + +### Maybe + +`Maybe` is used when a result doesn't have to exist. Unlike `null` in JavaScript, we are forced to handle that case. + +```elm +type Maybe a + = Just a + | Nothing +``` + +For example empty list doesn't have a head. + +```elm +> List.head + : List a -> Maybe a +``` + +We can use the `Maybe` type in `case` statement as any other custom type. + +```elm +hello : String -> String +hello name = + "Hello, " ++ name ++ "!" + + +greet : Maybe String -> String +greet maybeName = + case maybeName of + Just name -> + hello name + + Nothing -> + "Nobody's here" +``` + +[Maybe](https://package.elm-lang.org/packages/elm/core/latest/Maybe) package contains a handful of useful functions to simplify working with maybes. + +```elm +> Maybe.withDefault + : a -> Maybe a -> a + +> Maybe.withDefault "default" (Just "value") +"value" : String + +> Maybe.withDefault "default" Nothing +"default" : String +``` + +```elm +> Maybe.map + : (a -> b) -> Maybe a -> Maybe b + +> Maybe.map ((*) 2) (Just 4) +Just 8 : Maybe number + +> Maybe.map ((*) 2) Nothing +Nothing : Maybe number +``` + +```elm +greet2 : Maybe String -> String +greet2 maybeName = + Maybe.withDefault "Nobody's here" (Maybe.map hello maybeName) +``` + + + +### Result + +`Result` is used for computations that may fail. + +```elm +type Result error value + = Ok value + | Err error +``` + +For example, we cannot calculate area with of rectangle with negative sides. + +```elm +rectArea : Float -> Float -> Result String Float +rectArea a b = + if a < 0 || b < 0 then + Err "Cannot calculate area with negative sides" + else + Ok (a * b) +``` + +There are again helpful functions in [Result](https://package.elm-lang.org/packages/elm/core/latest/Result) package. + + + +### let expressions + +It is sometimes handy to define some expression within a function to avoid repetition. We have let expressions for that. + +```elm +cubeArea : Float -> Float +cubeArea edge = + let + face = + edge ^ 2 + in + 6 * face +``` + +### Operators |>, <|, >>, << + +Elm has several operators for chaining functions and function calls together. + +#### |> + +`|>` operator takes a value and a function and applies the function to the value. + +```elm +> (|>) + : a -> (a -> b) -> b +``` + +It is useful when chaining more steps together to write readable code. + +```elm +greet3 : Maybe String -> String +greet3 maybeName = + maybeName + |> Maybe.map hello + |> Maybe.withDefault "Nobody's here" +``` + +#### <| + +`<|` operator is the opposite. It takes a function and a value and apply the function to the value. + +```elm +> (<|) + : (a -> b) -> a -> b +``` + +It is useful to avoid parentheses, the same as `$` in Haskell. + +```elm +greet4 : Maybe String -> String +greet4 maybeName = + Maybe.withDefault "Nobody's here" <| Maybe.map hello maybeName +``` + +#### >> + +`>>` is used for function composition - `(f >> g) x == g(f x)`. + + +```elm +> (>>) + : (a -> b) -> (b -> c) -> a -> c +``` + +```elm +greet5 : Maybe String -> String +greet5 = + Maybe.map hello >> Maybe.withDefault "Nobody's here" +``` + + +#### << + +`>>` is used for function composition in an opposite direction - `(f << g) x == f(g x)`. This is same as `.` in Haskell. + +```elm +> (<<) + : (b -> c) -> (a -> b) -> a -> c +``` + +```elm +greet6 : Maybe String -> String +greet6 = + Maybe.withDefault "Nobody's here" << Maybe.map hello +``` + + +### Debug + +Elm has a [Debug](https://package.elm-lang.org/packages/elm-lang/core/latest/Debug) package intended for debugging. It +should not be used in production code. + +`log` function can be used to write a value to console. + +```elm +> Debug.log "value" 1 +value: 1 +1 : number +``` + +```elm +> 5 - Debug.log "number" 4 +number: 4 +1 : number +``` + + +## Packages + +Elm packages are published on [package.elm-lang.org](https://package.elm-lang.org). There is forced [semantic versioning](https://semver.org) for Elm packages. Therefore, you don't have to worry about breaking your application while updating packages. + +In 2018, Elm 0.19 was released, and not all packages have been updated yet, so you need to check first whether the package is supported in the latest Elm version. + + + +To install a package, we use `elm install` command in the project directory. + +``` +$ elm install elm-community/maybe-extra +Here is my plan: + + Add: + elm-community/maybe-extra 5.0.0 + +Would you like me to update your elm.json accordingly? [Y/n]: y +Dependencies loaded from local cache. +Dependencies ready! +``` + +Then we can use the new package the same as we used our package before: + +```elm +> import Maybe.Extra exposing (isNothing) + +> isNothing Nothing +True : Bool + +> isNothing (Just 2) +False : Bool +``` + + + +## The Elm Architecture (TEA) + +The Elm Architecture is a pattern used by Elm applications to define the architecture. It is perfect for modularity, refactoring, code reuse and testing. It is easy to keep even the complex applications clean and maintainable with the TEA. +The Elm application has three main parts: + +- **Model** - The state of the application. +- **Update** - How to change the state. +- **View** - How to display the state. + +There are also Subscribers and Commands. We will talk about them later. + +![The Elm Architecture](./images/tea.png) + + +### Example + +Example form [Elm guide](https://guide.elm-lang.org/#a-quick-sample): + +```elm +import Browser +import Html exposing (Html, button, div, text) +import Html.Events exposing (onClick) + +main = + Browser.sandbox { init = 0, update = update, view = view } + +type Msg = Increment | Decrement + +update msg model = + case msg of + Increment -> + model + 1 + + Decrement -> + model - 1 + +view model = + div [] + [ button [ onClick Decrement ] [ text "-" ] + , div [] [ text (String.fromInt model) ] + , button [ onClick Increment ] [ text "+" ] + ] +``` + +## Running the Elm application + +### Elm Reactor + +It is a quick and simple tool to run Elm project during development. Run `elm reactor` in the project root. It starts a server at [http://localhost:8000](http://localhost:8000) where you can navigate to Elm files. + + +### Elm Make + +Tool for building Elm project. It can compile to HTML or JavaScript. For example: + +``` +$ elm make src/Main.elm --output=main.html +``` + +Generates `main.html` file with the Elm application. + + + +## Further reading + +- [An Introduction to Elm](https://guide.elm-lang.org) +- [Elm Syntax](https://elm-lang.org/docs/syntax) +- [Blazing Fast HTML](https://elm-lang.org/blog/blazing-fast-html-round-two) +- [Small Assets without the Headache](https://elm-lang.org/blog/small-assets-without-the-headache) +- [Elm in Production: Surprises & Pain Points](https://www.youtube.com/watch?v=LZj_1qVURL0) + diff --git a/tutorials/09_webapp.md b/tutorials/09_webapp.md deleted file mode 100644 index 6e30cd7..0000000 --- a/tutorials/09_webapp.md +++ /dev/null @@ -1,506 +0,0 @@ -# Web application in Haskell - -Haskell can be (of course) used for network communication and also for building various web applications. In this tutorial, we are going to look at basics of network communication in Haskell, some specialized libraries making it simpler, and then at web frameworks. - -## Network communication - -On the way to web applications, it is good to know how you can work with network communication on lower levels than is some web framework. - -### Sockets - -The most low-level solutions work directly with sockets via [Network.Socket](https://hackage.haskell.org/package/network/docs/Network-Socket.html) module. With that, you have a full control over the communication. Essentially the entire C socket API is exposed through this module, so if you are familiar with sockets from C, then it will be easy for you in Haskell: `bind`, `listen`, `receive`, `send`, `getAddrInfo`, etc. - -### Server-client demo with sockets - -There is a demo with echo server and client in the [Network.Socket](https://hackage.haskell.org/package/network/docs/Network-Socket.html) documentation. We will show a bit simpler example without forking. - -```haskell -{-# LANGUAGE OverloadedStrings #-} -module Main (main) where - -import qualified Control.Exception as E -import Control.Monad (unless, forever, void) -import qualified Data.ByteString as S -import qualified Data.ByteString.Char8 as C -import Network.Socket hiding (recv) -import Network.Socket.ByteString (recv, sendAll) - -main :: IO () -main = withSocketsDo $ do - addr <- mkAddr "localhost" "3000" - E.bracket (open addr) close loop - where - mkAddr host port = do - let hints = defaultHints { - addrFlags = [AI_PASSIVE] - , addrSocketType = Stream - } - addr:_ <- getAddrInfo (Just hints) (Just host) (Just port) - return addr - open addr = do - sock <- socket (addrFamily addr) (addrSocketType addr) (addrProtocol addr) - setSocketOption sock ReuseAddr 1 - bind sock (addrAddress addr) - listen sock 10 - return sock - loop sock = forever $ do - (conn, peer) <- accept sock - putStrLn $ "Connection from " ++ show peer - msg <- recv conn 1024 - C.putStrLn $ msg - unless (S.null msg) $ do - sendAll conn (S.reverse msg) - close conn -``` - -```haskell -{-# LANGUAGE OverloadedStrings #-} -module Main (main) where - -import qualified Control.Exception as E -import qualified Data.ByteString.Char8 as C -import Network.Socket hiding (recv) -import Network.Socket.ByteString (recv, sendAll) - -main :: IO () -main = withSocketsDo $ do - addr <- mkAddr "localhost" "3000" - E.bracket (open addr) close talk - where - mkAddr host port = do - let hints = defaultHints { addrSocketType = Stream } - addr:_ <- getAddrInfo (Just hints) (Just host) (Just port) - return addr - open addr = do - sock <- socket (addrFamily addr) (addrSocketType addr) (addrProtocol addr) - connect sock $ addrAddress addr - return sock - talk sock = do - putStrLn "What do you want to send?" - toSend <- getLine - sendAll sock (C.pack toSend) - msg <- recv sock 1024 - putStr "You received: " - C.putStrLn msg -``` - -A you can see, most of the work happens in `do` of `IO` and it highly resembles classic imperative programming. Indeed, networking and also most of UI is inherently not a show room of beautiful Haskell code, but it gets the job done ;-). - -### Specialized libraries - -Naturally, there are many specialized libraries that provide a higher-level interface for network communication than plain sockets when you want to work with some specific protocol (POP3, SMTP, SSH, or HTTP). Some are listed [here](https://wiki.haskell.org/Applications_and_libraries/Network) but you can find more on [Hackage](https://hackage.haskell.org). - -The need of REST API client is something very common. For writing a simple one, you may use [wreq](https://hackage.haskell.org/package/wreq) package. It provides simple but powerful lens-based API and it supports often-used techniques like OAuth, decompression, file upload, etc. - -```haskell --- GitHub API: list public repositories of user -{-# LANGUAGE OverloadedStrings #-} -import Control.Lens -import Data.Aeson.Lens -import Data.ByteString.Char8 hiding (putStrLn, getLine) -import qualified Data.Text as T (unpack) -import Data.Foldable -import Network.Wreq - - -mkURI :: String -> String -mkURI username = "https://api.github.com/users/" ++ username ++ "/repos" - -getRepos :: String -> IO [String] -getRepos username = do - r <- get (mkURI username) - return . fmap T.unpack $ r ^.. responseBody . values . key "full_name" . _String - -main :: IO () -main = do - putStrLn "Enter GitHub username:" - username <- getLine - repos <- getRepos username - putStrLn $ "## First 25 public repos: " - traverse_ putStrLn repos -``` - -## Web Frameworks overview - -As with other languages, you usually don't want to build a web application from the scratch which would bind ports, listen and parse requests and compose responses. For a higher abstraction, you might want to use a web framework. - -There are several frameworks in Haskell (see [here](https://wiki.haskell.org/Web/Frameworks)) and here is our list of the most used ones: - -- [Happstack](http://happstack.com) -- [Scotty](https://github.com/scotty-web/scotty) -- [Servant](https://haskell-servant.github.io) -- [Snap](http://snapframework.com) -- [Spock](https://www.spock.li) -- [Yesod](https://www.yesodweb.com) - -As you can see, there is quite an above-average offer of them. They mostly differ at the level of abstraction and scope: Scotty being relatively low-abstraction routing and middleware and Yesod being a complete solution including templating and persistence, everything on a high abstraction level. The choice depends on your preference and needs. As always, a higher abstraction means the system does a lot of job for yourself, you write fewer code, which means a higher effectiveness and less bugs. On the other hand, you may face a [leaky abstraction problem](https://blog.codinghorror.com/all-abstractions-are-failed-abstractions/) at some point. - -We are going to show briefly Snap and Yesod, because they are used often and then we will go deeper with our favourite versatile Scotty. Next time, we will look at a different one to build quickly a REST API for our front-end app(s). - -However, before we dive into the topic, there is one more note, which may excite you. In the first lecture, we explained what a referential transparency is and that it brings certain qualities to code -- ability to reason about, reusability, testability, parallelism "for free". In the case of web frameworks, you can experience reusability coming from reference transparency very clearly: web frameworks are typically built of certain independent, shared components like a web server ([warp](https://hackage.haskell.org/package/warp)) or a web application interface ([wai](https://hackage.haskell.org/package/wai)). These highly-specialised components are developed independently, tested independently and as such, the whole ecosystem exercises an unparalleled separation of concerns and thanks to it, it is easier evolvable, reliable and lively. Moreover, you can use the low-level components independently and integrate them easily in another framework or even an non-web application -- you can e.g. use [templating from Yesod](https://hackage.haskell.org/package/shakespeare) in Scotty, if you like, or its [persistence layer](https://hackage.haskell.org/package/persistent) in a simple CLI application. - -### Snap - -[Snap](http://snapframework.com) is a simple web development framework for UNIX systems, written in the Haskell programming language. It consists of: - -* A fast HTTP server library -* A sensible and clean monad for web programming -* An HTML-based templating system for generating pages (heist) - -More examples are in the [documentation](http://snapframework.com/docs). - -#### "Hello World" - -```haskell -import Snap - -site :: Snap () -site = - ifTop (writeBS "hello world") <|> - route [ ("foo", writeBS "bar") - , ("echo/:echoparam", echoHandler) - ] <|> - dir "static" (serveDirectory ".") - -echoHandler :: Snap () -echoHandler = do - param <- getParam "echoparam" - maybe (writeBS "must specify echo/param in URL") - writeBS param - -main :: IO () -main = quickHttpServe site -``` - -#### Snaplets - -Snap also has a very nice philosophy in form of an optional system for building reusable pieces web functionality called “snaplets”. Snaplets make it easy to share and reuse common code across multiple web apps. The default snaplets let you get a full-featured web application up and running very fast. - -If you want to build such application, read [this](http://snapframework.com/docs/tutorials/snaplets-tutorial). - -### Yesod - -[Yesod](https://www.yesodweb.com) is a Haskell web framework for productive development of type-safe, RESTful, high performance web applications. It builds on Haskell features such as compile-time errors (instead of runtime), seamlessly asynchronous computation, scalability, good performance and light-weight syntax. - -Another advantage of Yesod is comprehensive documentation including: - -* [quick start guide](https://www.yesodweb.com/page/quickstart), -* [book](https://www.yesodweb.com/book) (O'Reilly), -* [screencasts](https://www.yesodweb.com/page/screencasts), -* and [cookbook](https://github.com/yesodweb/yesod-cookbook). - -If that is not enough you can ask the [community](https://www.yesodweb.com/page/community). - -Yesod is a "Mercedes" of Haskell web frameworks. It means a lot of comfort is prepared for you to enjoy, however it also means that people needing an agile small car for Italian crooked streets may experience troubles ;-). - -#### "Hello World" - -From the following example, you can see that Yesod uses a lot of *Template Haskell* which makes it a little bit hard to get at the beginning, but it enchants you with a lot of pleasant magic. And yes, every magic has some costs ;-) (see the previous lecture). - -```haskell -{-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE QuasiQuotes #-} -{-# LANGUAGE TemplateHaskell #-} -{-# LANGUAGE TypeFamilies #-} -import Yesod - -data HelloWorld = HelloWorld - -mkYesod "HelloWorld" [parseRoutes| -/ HomeR GET -|] - -instance Yesod HelloWorld - -getHomeR :: Handler Html -getHomeR = defaultLayout [whamlet|Hello World!|] - -main :: IO () -main = warp 3000 HelloWorld -``` - -### Scotty - -[Scotty](https://github.com/scotty-web/scotty) is another Haskell web framework inspired by Ruby's [Sinatra](http://sinatrarb.com), using [WAI](https://hackage.haskell.org/package/wai) and [Warp](https://hackage.haskell.org/package/warp) (a fast, light-weight web server for WAI applications). You can write your own application just with WAI (Web Application Interface), but Scotty provides you with abstractions from a low-level communication. Sadly, there is not so much documentation about Scotty, everything is just on [GitHub](https://github.com/scotty-web/scotty). Scotty uses primarily [Blaze HTML](https://hackage.haskell.org/package/blaze-html) for HTML "templates", however, as we explained, you may also integrate it with any templating library you like. - -#### "Hello World" - -```haskell -{-# LANGUAGE OverloadedStrings #-} -import Web.Scotty - -main = scotty 3000 $ do - get "/" $ do - html "Hello World!" -``` - -Surprisingly easy, right?! - -#### Blaze templates - -One of the well-known and widely used solution for HTML templates is [Blaze HTML](https://hackage.haskell.org/package/blaze-html). It is "a blazingly fast HTML combinator library for the Haskell programming language". A huge advantage of Blaze is that you write HTML via HTML-like lightweight DSL in Haskell with the great type system. Blaze and Haskell won't allow you to make a non-sense HTML, although it does not check a full conformity, of course. - -```haskell -{-# LANGUAGE OverloadedStrings #-} -import Data.Text as T -import Text.Blaze.Html5 as H hiding (main) -import Text.Blaze.Html5.Attributes as A -import Text.Blaze.Html.Renderer.Pretty (renderHtml) - -type User = String - -userInfo :: Maybe User -> Html -userInfo u = H.div ! A.id "user-info" $ case u of - Nothing -> - a ! href "/login" $ "Please login." - Just user -> do - "Logged in as " - toHtml $ T.pack user - -somePage :: Maybe User -> Html -somePage u = html $ do - H.head $ do - H.title "Some page." - H.body $ do - userInfo u - "The rest of the page." - -main :: IO () -main = putStr . renderHtml $ somePage (Just "Marek") -``` - -An interesting tool, that you might find useful, is [blaze-from-html](https://hackage.haskell.org/package/blaze-from-html). - -You might ask "What about styles?" or "What if want to have some JavaScript there?". For styles, there is [clay](https://hackage.haskell.org/package/clay) - a CSS preprocessor like LESS and Sass, but implemented as an embedded domain specific language (EDSL) in Haskell. Similarly to Blaze, you write CSS in Haskell. For JavaScript, stay tuned for the next tutorial ;-). - -#### Hastache templates - -If you are already familiar with some web development, you've probably heard about the popular [{{ mustache }}](http://mustache.github.io) templates. In Haskell, we have Haskell implementation of Mustache templates called [hastache](https://hackage.haskell.org/package/hastache). - -```haskell -import Text.Hastache -import Text.Hastache.Context -import qualified Data.Text.Lazy.IO as TL - -main = hastacheStr defaultConfig (encodeStr template) (mkStrContext context) - >>= TL.putStrLn - -template = "Hello, {{#reverse}}world{{/reverse}}! We know you, {{name}}!" - -context "reverse" = MuLambda (reverse . decodeStr) -context "name" = MuVariable "Haskell" -``` - -A useful source of information what can you do with Hastache are [examples](https://github.com/lymar/hastache/tree/master/examples). - -#### Databases - -Again, several abstraction levels are available. First, you can employ a low-level approach where you incorporate SQL in the code. For that, you can usually use module `Database.X` where `X` is type of datase: - -- [Database.SQLite](http://hackage.haskell.org/package/sqlite-simple) -- [Database.MySQL](http://hackage.haskell.org/package/mysql) -- [Database.PostgreSQL](http://hackage.haskell.org/package/PostgreSQL) -- [Database.MongoDB](http://hackage.haskell.org/package/mongoDB) -- [Database.Redis](http://hackage.haskell.org/package/redis) -- etc. - -A slightly better services are provided by mid-level libraries: - -- [Database.MySQL.Simple](https://hackage.haskell.org/package/mysql-simple) -- [Database.PostgreSQL.Simple](https://hackage.haskell.org/package/postgresql-simple) - -Going higher with the abstraction, you can then use [Haskell Database Connectivity (HDBC)](http://hackage.haskell.org/package/HDBC) for SQL databases. A good introduction to HDBC is in [Chapter 21 - Using Databases](http://book.realworldhaskell.org/read/using-databases.html) of the [Real World Haskell](http://book.realworldhaskell.org/) book. - -```sql -CREATE TABLE test(id INTEGER PRIMARY KEY, str TEXT);\ -INSERT INTO test(str) VALUES ('test string 1'); -INSERT INTO test(str) VALUES ('test string 2'); -``` - -```haskell -{-# LANGUAGE OverloadedStrings #-} -import Control.Applicative -import Database.SQLite.Simple -import Database.SQLite.Simple.FromRow - -data TestField = TestField Int String deriving (Show) - -instance FromRow TestField where - fromRow = TestField <$> field <*> field - -main :: IO () -main = do - conn <- open "test.db" - execute conn "INSERT INTO test (str) VALUES (?)" - (Only ("test string 2" :: String)) - r <- query_ conn "SELECT * from test" :: IO [TestField] - mapM_ print r -``` - -#### Persistence with Persistent - -Now skyrocketing the abstraction to the heights of Template Haskell, we get to [persistent](https://hackage.haskell.org/package/persistent) that comes also with various [extensions](https://hackage.haskell.org/packages/search?terms=persistent). There is a nice documentation of this package in the Yesod [book](https://www.yesodweb.com/book/persistent), but, as we already explained, you can use it with any framework or even without any framework — just whenever you need to persist some data in a database. - -```haskell -{-# LANGUAGE EmptyDataDecls #-} -{-# LANGUAGE FlexibleContexts #-} -{-# LANGUAGE GADTs #-} -{-# LANGUAGE GeneralizedNewtypeDeriving #-} -{-# LANGUAGE MultiParamTypeClasses #-} -{-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE QuasiQuotes #-} -{-# LANGUAGE TemplateHaskell #-} -{-# LANGUAGE TypeFamilies #-} -import Control.Monad.IO.Class (liftIO) -import Database.Persist -import Database.Persist.Sqlite -import Database.Persist.TH - -share [mkPersist sqlSettings, mkMigrate "migrateAll"] [persistLowerCase| -Person - name String - age Int Maybe - deriving Show -BlogPost - title String - authorId PersonId - deriving Show -|] - -main :: IO () -main = runSqlite ":memory:" $ do - runMigration migrateAll :: IO () - - johnId <- insert $ Person "John Doe" $ Just 35 - janeId <- insert $ Person "Jane Doe" Nothing - - insert $ BlogPost "My fr1st p0st" johnId - insert $ BlogPost "One more for good measure" johnId - - oneJohnPost <- selectList [BlogPostAuthorId ==. johnId] [LimitTo 1] - liftIO $ print (oneJohnPost :: [Entity BlogPost]) - - john <- get johnId - liftIO $ print (john :: Maybe Person) - - delete janeId - deleteWhere [BlogPostAuthorId ==. johnId] -``` - -As you noticed, Persistent uses *Template Haskell* for a declaration of a persistent model. - -For other possibilities of persistence libraries, take a look [here](https://wiki.haskell.org/Web/Databases_and_Persistence) or search the [Hackage](https://hackage.haskell.org). - -## WAI and testing web apps - -There must be some interface between a web application and the web server where the application is running. You may have heard about something like [CGI](https://en.wikipedia.org/wiki/Common_Gateway_Interface), [FastCGI](https://en.wikipedia.org/wiki/FastCGI), [WSGI](https://cs.wikipedia.org/wiki/Web_Server_Gateway_Interface), or similar. As is [WSGI](https://cs.wikipedia.org/wiki/Web_Server_Gateway_Interface) for Python web applications, we have [Web Application Interface (WAI)](https://www.stackage.org/package/wai) in Haskell. - -### Web app with plain WAI - -It is possible to write a simple web application with just WAI and without any additional web framework. - -```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 -``` - -### HSpec & WAI - -Web applications in Haskell can be tested via WAI. All applications that conform with WAI can be tested in the same way like a black box - send a request and check the response. In our favourite [Hspec](https://hspec.github.io), there is an extension [hspec-wai](https://github.com/hspec/hspec-wai) that allows you to test web applications in a very easy and readable way, as we are used to with hspec. - -```haskell -{-# LANGUAGE OverloadedStrings, QuasiQuotes #-} -module Main (main) where - -import Test.Hspec -import Test.Hspec.Wai -import Test.Hspec.Wai.JSON - -import Network.Wai (Application) -import qualified Web.Scotty as S -import Data.Aeson (Value(..), object, (.=)) - -main :: IO () -main = hspec spec - -app :: IO Application -app = S.scottyApp $ do - S.get "/" $ do - S.text "hello" - - S.get "/some-json" $ do - S.json $ object ["foo" .= Number 23, "bar" .= Number 42] - -spec :: Spec -spec = with app $ do - describe "GET /" $ do - it "responds with 200" $ do - get "/" `shouldRespondWith` 200 - - it "responds with 'hello'" $ do - get "/" `shouldRespondWith` "hello" - - it "responds with 200 / 'hello'" $ do - get "/" `shouldRespondWith` "hello" {matchStatus = 200} - - it "has 'Content-Type: text/plain; charset=utf-8'" $ do - get "/" `shouldRespondWith` 200 {matchHeaders = ["Content-Type" <:> "text/plain; charset=utf-8"]} - - describe "GET /some-json" $ do - it "responds with some JSON" $ do - get "/some-json" `shouldRespondWith` [json|{foo: 23, bar: 42}|] -``` - -This is just a simple (but often sufficient) example. Of course, you can test much more: - -- https://begriffs.com/posts/2014-10-19-warp-server-controller-test.html -- https://www.spock.li/tutorials/testing - -## Example apps: - -Here are a few examples of simple and more complex web apps: - -* [dbushenko/scotty-blog](https://github.com/dbushenko/scotty-blog) -* [DataStewardshipWizard/ds-wizard](https://github.com/DataStewardshipWizard/ds-wizard/tree/master/DSServer) -* [DataStewardshipWizard/dsw-server](https://github.com/DataStewardshipWizard/dsw-server) - - -## Continuation-style web development - -We would also like to raise your attention to an interesting approach to web development based on the [continuation](https://wiki.haskell.org/Continuation). A continuation is "something" that enables you to save the state of computation, suspend it (do something else) and later resume it. This "something" may be a first-class language feature (such as in Scheme), or a library feature -- in Haskell, surprisingly, we have a continuation monad ;-). - -A need for continuation occurs typically in web development (and generally in UI development) when you want a modal dialogue. Today, most of the dialogues are handled on client-side, however if you need to do a modal dialogue on server-side, it is hard -- HTTP behaves like a programming language, which does not have subroutines, only GOTOs (URLSs are the 'line numbers'). Continuation can provide the missing abstraction here, which is embodied in the [MFlow](http://mflowdemo.herokuapp.com) library. Sadly, the project seems abandoned for several years. - -At the same time, the continuation-style web server programming is typically the first choice in the Smalltalk (OOP) world -- [Seaside](http://seaside.st/) is purely continuation-based, and as such it gives a "desktop programming" experience for the web development resulting in no need of dealing with routing and URLs. As for the FP world, continuation-style web programming is surprisingly not used much in practice, but there are solutions such as the [Racket web server](https://docs.racket-lang.org/web-server/index.html) or [cl-weblocks](https://www.cliki.net/cl-weblocks) in Common Lisp. - - -The next time, we will deal a bit with frontend technologies for Haskell, functional reactive programming and [The JavaScript problem](https://wiki.haskell.org/The_JavaScript_Problem). So you will also see how to develop server-side and client-side separately and connect them through a (REST) API. - -## Task assignment - -The homework to complete a simple web app is in repository [MI-AFP/hw09](https://github.com/MI-AFP/hw09). - -## Further reading - -* [YesodBook - Persistent](https://www.yesodweb.com/book/persistent) -* [adit.io - Making A Website With Haskell](http://adit.io/posts/2013-04-15-making-a-website-with-haskell.html) -* [24 Days of Hackage: blaze-html](https://ocharles.org.uk/blog/posts/2012-12-22-24-days-of-hackage-blaze.html) -* [Haskell web frameworks](https://wiki.haskell.org/Web/Frameworks) -* [Reddit: What Haskell web framework do you use and why? ](https://www.reddit.com/r/haskell/comments/332s1k/what_haskell_web_framework_do_you_use_and_why/) -* [Reddit: Web development using Haskell](https://www.reddit.com/r/haskell/comments/2wfap0/web_development_using_haskell/) -* [Is Haskell a Good Choice for Web Applications?](http://jekor.com/article/is-haskell-a-good-choice-for-web-applications) diff --git a/tutorials/10_frontend-frp.md b/tutorials/10_frontend-frp.md deleted file mode 100644 index 01b7d19..0000000 --- a/tutorials/10_frontend-frp.md +++ /dev/null @@ -1,417 +0,0 @@ -# Frontend and FRP - -In the previous tutorial, we focused on web frameworks and especially on building a backend and some frontend generation by blaze or hastache on the server-side. This time, we will cover building frontend apps that are standalone or communicate with backend via (REST) API. At the end of this tutorial, there is a section about a very interesting concept *Functional Reactive Programming* that is important when building purely functional user interfaces. - -## Haskell and Haskell-like frontends - -### The JavaScript Problem - -We all know what is JavaScript -- it is a dynamic, weakly typed, prototype-based and multi-paradigm programming language. Together with HTML and CSS, it is one of the three core technologies of the World Wide Web. JavaScript is used for interactive web pages and thus is an essential part of modern web applications. These days, JavaScript is often also used for the server-side or even desktop applications (e.g. [Atom editor](https://atom.io/)). - -As obvious from above, we need JavaScript. On the other hand, JavaScript has some issues that make working with it inconvenient and make developing software harder. Some things are improving with time (newer versions of [ECMAScript](https://en.wikipedia.org/wiki/ECMAScript)) but most of them remain from the very basic principles of the language: weak-typing, late binding, [weird automatic conversions](https://youtu.be/ryJSRZzAvUs), `this` behaviour, and lack of static types. There are solutions in the form of "improved syntax" like [CoffeeScript](http://coffeescript.org) and [TypeScript](https://www.typescriptlang.org) that are dealing with some of those... - -But since we are now Haskellists, we would like to have something even better - a Haskell-like JavaScript to solve these problems. Luckily, we are not the only ones and there are already several solutions how to compile Haskell to JavaScript or similar languages based on Haskell that are adapted for this very specific purpose. - -Take a look at [Slant - What are the best solutions to "The JavaScript Problem"?](https://www.slant.co/topics/1515/~solutions-to-the-javascript-problem). We are going to look at some now! - -### GHCJS - -GHCJS is a Haskell to JavaScript compiler that uses the GHC API. - -GHCJS supports many modern Haskell features, including: - - * All type system extensions supported by GHC - * Lightweight preemptive threading with blackholes, MVar, STM, asynchronous exceptions - * Weak references, CAF deallocation, StableName, StablePtr - * Unboxed arrays, emulated pointers - * Integer support through [JSBN](http://www-cs-students.stanford.edu/~tjw/jsbn/), 32 and 64 bit signed and unsigned arithmetic (`Word64`, `Int32` etc.) - * Cost-centres, stack traces - * Cabal support, GHCJS has its own package database - -And some JavaScript-specific features: - - * new JavaScriptFFI extension, with convenient import patterns, asynchronous FFI and a JSVal FFI type, - * synchronous and asynchronous threads. - -- Project: [ghcjs/ghcjs](https://github.com/ghcjs/ghcjs) -- Nice example: [Full stack web Haskell with Servant and GHCJS](http://blog.wuzzeb.org/full-stack-web-haskell/index.html) - -The dark side of GHCJS is big resulting JavaScript code and it is hard to make work (just try for yourself ;-). Also, the community around GHCJS and its ecosystem is not the most active one. - -### Haste - -[Haste](https://haste-lang.org) is an implementation of the Haskell functional programming language, geared towards web applications. Haste is based on the GHC, which means that it supports the full Haskell language, including GHC extensions and produces highly optimized code but comes with an extended set of standard libraries. However, compared to GHCJS, Template Haskell is not supported. Haste supports modern web technologies such as WebSockets, LocalStorage, Canvas, etc. out of the box. In addition, Haste comes pre-packaged with facilities for preemptive multitasking, working with binary data and other niceties. - -A Haste program can be compiled into a single JavaScript file, much like traditional browser-side programs, or into a JavaScript file and a server-side binary, with strongly typed communication between the two. In essence, Haste lets you write your client-server web application as a single, type-safe program, rather than two separate programs that just happen to talk to each other over some web API as is traditional. - -You don’t need to throw away all of your old code to start using Haste. In addition to the standard Haskell FFI, Haste provides its own flexible mechanism for easy Haskell-JavaScript integration, using fancy type magic to allow data of any type to be used by both Haskell and JavaScript code with minimal effort. - -Haste programs are more compact to GHCJS. While a certain increase in code size over hand-rolled JavaScript is unavoidable, an optimized but uncompressed Haste program is normally less than 3x the size of an equivalent hand-written program and the compiler takes special care to produce minifiable code, making the latency penalty of using Haste minimal. Sadly, similarly to GHCJS, the project is not much active (although not dead), the ecosystem, documentation and tutorials are not rich. - -- Examples: [valderman/haste-compiler](https://github.com/valderman/haste-compiler/tree/master/examples) -- API doc: [haste-compiler-0.5.5.0: Haskell To ECMAScript compiler](https://haste-lang.org/docs/haddock/0.5.5/) -- Our example: [DataStewardshipWizard/ds-wizard](https://github.com/DataStewardshipWizard/ds-wizard) and [DataStewardshipWizard/ds-form-engine](https://github.com/DataStewardshipWizard/ds-form-engine), built around JQuery bindings. - -### Miso - -**Miso** is a small "[isomorphic](http://nerds.airbnb.com/isomorphic-javascript-future-web-apps/)" [Haskell](https://www.haskell.org/) front-end framework for quickly building highly interactive single-page web applications. It features a virtual-dom, diffing / patching algorithm, attribute, and property normalization, event delegation, event batching, SVG, Server-sent events, Websockets, type-safe [servant](https://haskell-servant.github.io/)-style routing and an extensible Subscription-based subsystem. Inspired by [Elm](http://elm-lang.org/), [Redux](http://redux.js.org/) and [Bobril](http://github.com/bobris/bobril). **Miso** is pure by default, but side effects (like `XHR`) can be introduced into the system via the `Effect` data type. **Miso** makes heavy use of the [GHCJS](https://github.com/ghcjs/ghcjs) FFI and therefore has minimal dependencies. **Miso** can be considered a shallow [embedded domain-specific language](https://wiki.haskell.org/Embedded_domain_specific_language) for modern web programming. ([dmjio/miso](https://github.com/dmjio/miso/edit/master/README.md)) - -### PureScript - -PureScript is a strict, purely functional programming language inspired by Haskell which compiles to readable JavaScript with a simple foreign function interface and no runtime dependency. PureScript is sometimes called "Haskell done right" -- it learned from the long Haskell history, took the best of it (type system, category theory), refactored some parts, mostly basic libraries structure (Prelude) and added some goodies (records and row polymorphism) -- here is the overview of [differences](https://github.com/purescript/documentation/blob/master/language/Differences-from-Haskell.md). - -- Website: [purescript.org](http://www.purescript.org) -- Guide: [leanpub.com/purescript](https://leanpub.com/purescript/read) - -Although the ecosystem and documentation is considerably better than GHCJS's and Haste's, it has not reached broader adoption, (yet?). - -### Elm - -Elm is a functional language that compiles to JavaScript. It is not a Haskell, but language inspired by and in some ways very similar to Haskell (see [main differences](https://gist.github.com/cobalamin/c1b83f5626df1409b512ce2faf05cf84)) - it is more different from Haskell than PureScript. It offers an interesting type-safe alternative to projects such as React as a tool for creating websites and web apps. Elm has a very strong emphasis on simplicity, ease-of-use, and quality tooling. The compiler of Elm is written in Haskell and you can work with Elm in Haskell with [Language.Elm](https://hackage.haskell.org/package/Elm). - -- Guide: [guide.elm-lang.org](https://guide.elm-lang.org) -- Examples: [elm-lang.org/examples](http://elm-lang.org/examples) -- Our example: [DataStewardshipWizard/dsw-client](https://github.com/DataStewardshipWizard/dsw-client) - -Simplicity, good ecosystem, documentation and active community earned Elm quite some interest. At the same time, the lack of type classes hinders flexibility, reuse and DRY. - -### ReasonML - -[ReasonML](https://reasonml.github.io/) is a notable project that should be mentioned here. Although it is not based on Haskell, it shares the same ancestor -- the [ML language](https://en.wikipedia.org/wiki/ML_(programming_language)), the first FP language with Hindley-Milner type system that influenced many today's languages. ML (and its newer dialects Standard ML, Caml and OCaml) are not entirely pure (and as such they are being scorned by Haskellists ;-), but you find a strong type system, ADTs, etc. there, as well. - -What makes ReasonML notable is that it was created by Facebook and thus gets a strong warp. 50% of FB Messanger has been rewritten into ReasonML and there are quite some [impressive statistics](https://reasonml.github.io/blog/2017/09/08/messenger-50-reason.html). It is a project that is worth at least to be observed. - -## FRP - Functional Reactive Programming - -Functional reactive programming (FRP) is a programming paradigm for asynchronous dataflow programming using the building blocks of functional programming (such as `map`, `filter`, `fold`s, higher-order functions, etc.). It has been used often for programming graphical user interfaces (GUIs), robotics, and music, aiming to simplify these problems by explicitly modelling the concept of time. A good example to imagine what is it about is an ordinary spreadsheet calculator (Excel). You have cells that compute something from different cells and when you edit some, the related will recalculate - you do not tell what should be recalculated nor recalculate by yourself, all changes automatically propagate. See? It is "action and reaction"! - -The original idea was introduced more than 20 years ago by Conal Elliott in his [paper](http://conal.net/papers/ActiveVRML/ActiveVRML.pdf). Since then, he published several [other papers](http://conal.net/papers/) and there are also videos about the [FRP essence](https://begriffs.com/posts/2015-07-22-essence-of-frp.html). Several different trade-off approaches has been developed for practical usage. You can see the list [here](https://wiki.haskell.org/Functional_Reactive_Programming#Libraries). Also, of course, you can find FRP implementations in other languages than Haskell. - -### FRP principles - -For better understanding what FRP is about and what are the basic concepts, please read [The introduction to Reactive Programming you've been missing (by @andrestaltz)](https://gist.github.com/staltz/868e7e9bc2a7b8c1f754)... - -### Reactive - -[Reactive](https://hackage.haskell.org/package/reactive) is a simple foundation for programming reactive systems functionally. Like Fran/FRP, it has a notion of (reactive) behaviours and events. Unlike most previous FRP implementations, Reactive has a hybrid demand/data-driven implementation, as described in the paper "Push-pull functional reactive programming", http://conal.net/papers/push-pull-frp/. - -Sadly the documentation, tutorials, and examples are not currently in a good shape. - -### Reactive-banana - -[Reactive-banana](https://wiki.haskell.org/Reactive-banana) is meant to be used in conjunction with existing libraries that are specific to your problem domain. For instance, you can hook it into any event-based GUI framework, like `wxHaskell` or `Gtk2Hs`. Several helper packages such as `reactive-banana-wx` provide a small amount of glue code that can make life easier. - -The goal of the library is to provide a solid foundation. - -* Programmers interested implementing FRP will have a reference for a simple semantics with a working implementation. The library stays close to the semantics pioneered by Conal Elliott. -* The library features an efficient implementation. No more spooky time leaks, predicting space & time usage should be straightforward. -* A plethora of [example code](https://wiki.haskell.org/Reactive-banana/Examples) helps with getting started. - -### Yampa - -[Yampa](https://wiki.haskell.org/Yampa) is a domain-specific embedded language for programming of hybrid (discrete and continuous time) systems using the concepts of FRP. Yampa is structured using Arrows, which greatly reduce the chance of introducing space- and time-leaks into reactive, time-varying systems. - -![Signals in Yampa](https://wiki.haskell.org/wikiupload/thumb/1/10/Yampa_signal_functions.svg/624px-Yampa_signal_functions.svg.png) - -## Reactive programming with Elm - -We introduced Elm as a "convenient language" and it also supports reactive programming, so let us show a simple example to demonstrate the clean [architecture of Elm apps](https://guide.elm-lang.org/architecture/) - Model, View, and Update. This app will be just one module `Main` (although you can have multi-module apps just as in Haskell) and it will have 4 pages: - -1. Simple static landing -2. Unit converter for metres, yards, feet, and inches (reactive update: Update->Model->View cycle) -3. GitHub API info about user (demonstrate HTTP communication with some API) -4. Not found page as default - -You need to install [Node.js](https://nodejs.org/en/) and [Elm](https://guide.elm-lang.org/install.html). Then you can use `elm-repl` to try something (like GHCi), `elm-reactor` for development, `elm-package` to work with dependencies, and `elm-make` to build it into JavaScript. - -In this example we need some dependencies so the final `elm-package.json` looks like this: - -```json -{ - "version": "1.0.0", - "summary": "Example simple Elm project", - "repository": "https://github.com/MI-AFP/elm-example.git", - "license": "MIT", - "source-directories": [ - "." - ], - "exposed-modules": [], - "dependencies": { - "elm-lang/core": "5.1.1 <= v < 6.0.0", - "elm-lang/html": "2.0.0 <= v < 3.0.0", - "elm-lang/http": "1.0.0 <= v < 2.0.0", - "elm-lang/navigation": "2.1.0 <= v < 3.0.0", - "evancz/url-parser": "2.0.1 <= v < 3.0.0", - "rundis/elm-bootstrap": "4.0.0 <= v < 5.0.0" - }, - "elm-version": "0.18.0 <= v < 0.19.0" -} -``` - -```elm -module Main exposing (..) - -import Html exposing (..) -import Html.Attributes exposing (..) -import Html.Events exposing (onInput) -import Http -import Navigation exposing (Location) -import UrlParser exposing (()) - --- Entrypoint (we use Navigation) -main : Program Never Model Msg -main = Navigation.program UrlChange - { view = view - , update = update - , subscriptions = (\_ -> Sub.none) - , init = init - } - --- Model = state of the app -type alias Model = - { page : Page - , metres : Float - , token : GitHubToken - , githubData : String - } - --- Page = enum of different views -type Page - = Home - | UnitConverter - | GitHubInfo - | NotFound - --- Own type for GitHub token -type GitHubToken - = Valid String - | Invalid String - --- Units for the conversion -type LengthUnit - = Metres - | Inches - | Yards - | Feets - --- Types of messages in the app with content type(s) -type Msg - = UrlChange Location - | UnitUpdate LengthUnit String - | TokenUpdate String - | GitHubResponse (Result Http.Error String) - --- Initial app state and command -init : Location -> ( Model, Cmd Msg ) -init location = urlUpdate location { page = Home - , metres = 0 - , token = Invalid "" - , githubData = "" - } - --- Update (when message comes, update model), this is just "router" -update : Msg -> Model -> (Model, Cmd Msg) -update msg model = - case msg of - UrlChange location -> - urlUpdate location model - UnitUpdate lu str -> - metresUpdate lu str model - TokenUpdate str -> - tokenUpdate str model - GitHubResponse res -> - githubUpdate res model - -githubUpdate : (Result Http.Error String) -> Model -> (Model, Cmd Msg) -githubUpdate res model = - case res of - Ok str -> ({ model | githubData = str }, Cmd.none) - Err _ -> ({ model | githubData = "Error!" }, Cmd.none) - -tokenUpdate : String -> Model -> (Model, Cmd Msg) -tokenUpdate str model = - if isTokenValid str then - ( { model | token = Valid str }, gitHubInfoRequest str ) - else - ( { model | token = Invalid str }, Cmd.none ) - --- Send request to GitHub and then it will send appropriate message in this app -gitHubInfoRequest : String -> Cmd Msg -gitHubInfoRequest token = - Http.send GitHubResponse <| Http.request - { method = "GET" - , headers = [ Http.header "Authorization" ("token " ++ token)] - , url = "https://api.github.com/user" - , body = Http.emptyBody - , expect = Http.expectString - , timeout = Nothing - , withCredentials = False - } - -isTokenValid : String -> Bool -isTokenValid str = String.length str == 40 - -metresUpdate : LengthUnit -> String -> Model -> ( Model, Cmd Msg ) -metresUpdate lu x model = - case String.toFloat x of - Ok v -> ( { model | metres = v / (unitCoefficient lu)}, Cmd.none ) - Err _ -> ( model, Cmd.none ) - -urlUpdate : Navigation.Location -> Model -> ( Model, Cmd Msg ) -urlUpdate location model = - case decode location of - Nothing -> - ( { model | page = NotFound }, Cmd.none ) - Just route -> - ( { model | page = route }, Cmd.none ) - -decode : Location -> Maybe Page -decode location = - UrlParser.parseHash routeParser location - -routeParser : UrlParser.Parser (Page -> a) a -routeParser = - UrlParser.oneOf - [ UrlParser.map Home UrlParser.top - , UrlParser.map UnitConverter (UrlParser.s "unit-converter") - , UrlParser.map GitHubInfo (UrlParser.s "github-info") - ] - ---> VIEW -view : Model -> Html Msg -view model = - div [] - [ menu model - , mainContent model - ] - -menu : Model -> Html Msg -menu model = - div [] - [ viewLink "" "Home" - , viewLink "unit-converter" "Unit Converter" - , viewLink "github-info" "GitHub Info" - ] - -viewLink : String -> String -> Html msg -viewLink slug name = - li [] [ a [ href ("#" ++ slug) ] [ text name ] ] - -mainContent : Model -> Html Msg -mainContent model = - div [] ( - case model.page of - Home -> - pageHome model - UnitConverter -> - pageUnitConverter model - GitHubInfo -> - pageGitHubInfo model - NotFound -> - pageNotFound - ) - -pageHome : Model -> List (Html Msg) -pageHome model = - [ h1 [] [ text "Home" ] - , p [] [ text "This is very simple Elm example" ] - , hr [] [] - , p [] [ text "Enjoy learning " - , a [href "http://elm-lang.org"] [text "Elm"] - , text "!" - ] - ] - -pageUnitConverter : Model -> List (Html Msg) -pageUnitConverter model = - [ h1 [] [ text "Unit Converter" ] - , hr [] [] - , makeUnitInput Metres model - , makeUnitInput Inches model - , makeUnitInput Feets model - , makeUnitInput Yards model - ] - -makeUnitInput : LengthUnit -> Model -> Html Msg -makeUnitInput lu model = - div [] - [ label [] [text (unitToString lu)] - , input [ type_ "number" - , onInput (UnitUpdate lu) - , value (toString (computeUnit lu model)) - ] - [] - ] - -pageGitHubInfo : Model -> List (Html Msg) -pageGitHubInfo model = - [ h1 [] [ text "GitHub Info" ] - , div [] - [ label [] [text "GitHub token: "] - , input [ type_ "text" - , onInput TokenUpdate - , value (tokenToString model.token) - ] - [] - ] - , case model.token of - Valid token -> githubInfo model - Invalid _ -> invalidTokenMsg - ] - -githubInfo : Model -> (Html Msg) -githubInfo model = - pre [] [text (model.githubData)] - - -invalidTokenMsg : (Html Msg) -invalidTokenMsg = - div [] - [ p [] [text "Your token is not valid (40 chars required)"] - ] - -tokenToString : GitHubToken -> String -tokenToString t = - case t of - Valid s -> s - Invalid s -> s - -pageNotFound : List (Html Msg) -pageNotFound = - [ h1 [] [ text "Not found" ] - , text "Sorry couldn't find that page" - ] - ---> LOGIC -unitToString : LengthUnit -> String -unitToString lu = - case lu of - Metres -> "Metres" - Inches -> "Inches" - Yards -> "Yards" - Feets -> "Feets" - -computeUnit : LengthUnit -> Model -> Float -computeUnit lu model = model.metres * (unitCoefficient lu) - -unitCoefficient : LengthUnit -> Float -unitCoefficient lu = - case lu of - Metres -> 1 - Inches -> 39.3700787 - Yards -> 1.0936133 - Feets -> 3.2808399 -``` - -You can play with this app from [MI-AFP/elm-example](https://github.com/MI-AFP/elm-example). - -## Task assignment - -The homework to create a simple frontend for a REST API described in the repository [MI-AFP/hw10](https://github.com/MI-AFP/hw10). - -## Further reading - -* [Haskell on the front end](https://www.reddit.com/r/haskell/comments/7ax2ji/haskell_on_the_front_end/) -* [Zdroják.cz - Elm (czech only)](https://www.zdrojak.cz/clanky/elm-uvod/) -* [gelisam/frp-zoo (FRP libs comparison)](https://github.com/gelisam/frp-zoo) -* [FRP explanation using reactive-banana](https://wiki.haskell.org/FRP_explanation_using_reactive-banana) diff --git a/tutorials/11_performance-debug.md b/tutorials/11_performance-debug.md deleted file mode 100644 index 264bad0..0000000 --- a/tutorials/11_performance-debug.md +++ /dev/null @@ -1,468 +0,0 @@ -# Performance and Debugging - -During this tutorial, we will take a look how to improve the performance of a Haskell program and how to debug it. We will use very simple example - [Fibonacci numbers](https://en.wikipedia.org/wiki/Fibonacci_number). - -```haskell -import System.Environment - --- | Naive recursive algorithm for n-th Fibonacci number -fibonacci :: Integer -> Integer -fibonacci 0 = 0 -fibonacci 1 = 1 -fibonacci n = fibonacci (n-1) + fibonacci (n-2) - -main :: IO () -main = do - args <- getArgs - print . fibonacci . read . head $ args -``` - -## Measuring time and memory - -When you want to check the performance of a program and compare two programs or algorithms in terms of time or memory consumption, you need to measure it. - -### Basic `time` - -The `time` command is one of the well-known Linux commands for programmers. It can be used to show how long a command takes to run. That makes it Very useful if you are a developer and you want to test the performance of your program or script. Especially to compare the time of programs written in other languages "from outside". For basic usage, you will get three numbers: - -- `real` = total time is taken to run the command (the same as if you use your normal stopwatch) -- `user` = amount of time that was spent in user mode -- `sys` = amount of time spent in kernel mode - -Then `user`+`sys` gives information how much actual CPU time your process used - in total on all cores. This number can be then higher than `real` if your program uses multiple threads. - -``` -% /usr/bin/time -p runhaskell FibonacciNaive.hs 25 -75025 -real 0.33 -user 0.31 -sys 0.01 -``` - -But `time` can do a bit more, you can tell how output should look like with additional "numbers" - number of page faults, average total memory use of the process in kilobytes, number of signals delivered to the process, number of socket messages received/sent by the process, exit status of the command, and many others. - -``` -% /usr/bin/time -f "Elapsed Time: %E\nExit Status: %X\nPage Faults: %F" runhaskell FibonacciNaive.hs 25 -75025 -Elapsed Time: 0:00.34 -Exit Status: 0 -Page Faults: 0 -``` - -### Benchmarking with Criterion - -If you are interested in such optimizations and improving your application or comparing various algorithms or their implementations, then you might find interesting to use a benchmarking library. In Haskell is the most used one called [Criterion](http://www.serpentine.com/criterion/). It provides a powerful but simple way to measure software performance. It provides both a framework for executing and analyzing benchmarks and a set of driver functions that makes it easy to build and run benchmarks and to analyze their results. - -For simple usage, you just need to work with the `defaultMain` from [Criterion.Main](https://hackage.haskell.org/package/criterion/docs/Criterion-Main.html) as they show in their example: - -```haskell -import Criterion.Main - --- | Naive recursive algorithm for n-th Fibonacci number -fibonacci :: Integer -> Integer -fibonacci 0 = 0 -fibonacci 1 = 1 -fibonacci n = fibonacci (n-1) + fibonacci (n-2) - -main :: IO () -main = defaultMain [ - bgroup "fib" [ bench " 5" $ whnf fibonacci 5 - , bench "10" $ whnf fibonacci 10 - , bench "25" $ whnf fibonacci 25 - ] - ] -``` - -It has very nice outputs with a form of interactive HTML pages with charts and comparisons and has many options to use. - -``` -% runhaskell FibonacciNaiveCriterion.hs -benchmarking fib/ 5 -time 7.319 μs (6.980 μs .. 7.821 μs) - 0.966 R² (0.934 R² .. 0.995 R²) -mean 7.248 μs (6.966 μs .. 7.847 μs) -std dev 1.321 μs (805.8 ns .. 2.043 μs) -variance introduced by outliers: 96% (severely inflated) - -benchmarking fib/10 -time 81.34 μs (81.15 μs .. 81.54 μs) - 1.000 R² (1.000 R² .. 1.000 R²) -mean 81.58 μs (81.38 μs .. 81.85 μs) -std dev 811.3 ns (577.5 ns .. 1.191 μs) - -benchmarking fib/25 -time 111.6 ms (110.5 ms .. 112.2 ms) - 1.000 R² (1.000 R² .. 1.000 R²) -mean 112.1 ms (111.7 ms .. 112.9 ms) -std dev 853.6 μs (534.0 μs .. 1.215 ms) -variance introduced by outliers: 11% (moderately inflated) - -runhaskell FibonacciNaiveCriterion.hs 15.98s user 0.04s system 99% cpu 16.055 total -``` - -### Measure allocations with Weigh - -The package [weigh](https://hackage.haskell.org/package/weigh) provides a simple interface to measure the memory usage of a Haskell value or function. - -```haskell -import Weigh - --- | Naive recursive algorithm for n-th Fibonacci number -fibonacci :: Integer -> Integer -fibonacci 0 = 0 -fibonacci 1 = 1 -fibonacci n = fibonacci (n-1) + fibonacci (n-2) - -main :: IO () -main = mainWith $ do - func "fib 5" fibonacci 5 - func "fib 10" fibonacci 10 - func "fib 25" fibonacci 25 -``` - -It provides a nice output as plain text table, but it is also possible to change the format to markdown. - -``` -% ./FibonacciNaiveWeigh - -Case Allocated GCs -fib 5 1,968 0 -fib 10 24,304 0 -fib 25 33,509,936 63 -``` - -## Performance - -Now we are able to measure something and compare algorithms, but how to improve the numbers we get if we really need it? - -### Basic ideas - -When you are not satisfied with the performance of your application, then before any sophisticated optimization steps by using strictness, unboxed types, calling FFI, etc., you should consider if you prefer faster application over better readability. Then another important thing to think about is design if it is not slow by using "naive" algorithm, using an inappropriate data structure (List instead of Set or Map), etc. - -**Always** rethink your own code before using other optimization techniques! - -```haskell -import System.Environment - --- | Improved recursive algorithm for n-th Fibonacci number -fibonacci :: Integer -> Integer -fibonacci = fib 0 1 - where - fib x _ 0 = x - fib x y n = fib y (x+y) (n-1) -- just "one-way" recursion! - -main :: IO () -main = do - args <- getArgs - print . fibonacci . read . head $ args -``` - -Just a very simple re-thinking can have some impact: - -``` -% /usr/bin/time -p runhaskell FibonacciBetter.hs 25 -75025 -real 0.24 -user 0.22 -sys 0.02 -``` - -``` -% runhaskell FibonacciBetterCriterion.hs -benchmarking fib/ 5 -time 3.412 μs (3.235 μs .. 3.591 μs) - 0.988 R² (0.983 R² .. 0.998 R²) -mean 3.191 μs (3.129 μs .. 3.277 μs) -std dev 253.6 ns (168.4 ns .. 360.9 ns) -variance introduced by outliers: 82% (severely inflated) - -benchmarking fib/10 -time 5.930 μs (5.871 μs .. 6.013 μs) - 0.997 R² (0.994 R² .. 0.998 R²) -mean 6.209 μs (6.075 μs .. 6.464 μs) -std dev 625.6 ns (377.2 ns .. 1.088 μs) -variance introduced by outliers: 87% (severely inflated) - -benchmarking fib/25 -time 14.53 μs (14.31 μs .. 14.90 μs) - 0.990 R² (0.972 R² .. 0.999 R²) -mean 14.78 μs (14.40 μs .. 15.89 μs) -std dev 1.953 μs (712.6 ns .. 4.110 μs) -variance introduced by outliers: 91% (severely inflated) - -runhaskell FibonacciBetterCriterion.hs 15.90s user 0.07s system 100% cpu 15.954 total -``` - -``` -% ./FibonacciBetterWeigh - -Case Allocated GCs -fib 5 872 0 -fib 10 1,712 0 -fib 25 37,000 0 -``` - -### Boxed vs. Unboxed types - -Now, we are going to briefly mention is the difference between boxed and unboxed types. Although it is a low-level concern and with regular Haskell programming, you can avoid these terms, it is good to know what is it about when you see it in other's code or in a documentation. - -To support laziness, parametric polymorphism, and other properties, by default Haskell data types are represented uniformly as a pointer to a closure on the heap. These are "boxed" values. An unboxed is represented directly by raw value (i.e., without any indirection). Using unboxed types can lead to time/space optimizations. Having always pointers to a heap-allocated object is fairly slow, so compilers attempt to replace these boxed values with unboxed raw values when possible. Unboxed values are a feature of some compilers that allow directly manipulating these low-level values. Since they behave differently than normal Haskell types, generally the type system is extended to type these unboxed values. - -In GHC, unboxed values have a hash mark as a suffix to their name. For instance, the unboxed representation of 42 is 42#. However, you can't pass them to polymorphic functions (like `show` for instance). To allow that, you need to use constructor `I#` that takes an unboxed integer and returns the `Int` (wraps). You can observe [kind](https://wiki.haskell.org/Kind) (*kind of type*, we will look again at kinds with typeclasses) of boxed and unboxed types: - -* By default, kind of type is `*` (try in GHCi: `:kind Int`) -* Kind of unboxed type is `#` (try in GHCi: `:kind Int#`) - -```haskell -{-# LANGUAGE MagicHash #-} -module Main where - -import GHC.Exts - --- | Naive recursive algorithm for n-th Fibonacci number with --- unboxed Int types -fibonacci :: Int# -> Int# -fibonacci 0# = 0# -fibonacci 1# = 1# -fibonacci n = fibonacci (n -# 1#) +# fibonacci (n -# 2#) - -main :: IO () -main = print (I# (fibonacci 25#)) -``` - -``` -% /usr/bin/time -p runhaskell FibonacciUnboxed.hs -75025 -real 0.30 -user 0.27 -sys 0.03 -``` - -For more information, visit [GHC.Exts]() and [GHC.Prim](). - -### Strictness with types - -In the previous lessons, we touched the topic of enforcing strictness with `!` in patterns ([bang patterns](https://ocharles.org.uk/blog/posts/2014-12-05-bang-patterns.html)) and in function application with `$!` operator. Similarly, we can use `!` with type fields like this: - -```haskell -data MyType = MyConstr Int !Int - -data MyRec = MyRecConstr { xA :: Int - , xB :: !Int - } -``` - -For both cases it means that when data constructor is evaluated, it must fully evaluate ([weak head normal form](https://wiki.haskell.org/Weak_head_normal_form)) the second parameter, but the first one will stay unevaluated in a lazy way. All depends on language implementation in the used compiler. - -#### Unpacking strict fields - -One of the most used optimization techniques when talking about unboxed types and strictness with [GHC] is [unpacking strict fields](https://wiki.haskell.org/Performance/Data_types#Unpacking_strict_fields). When a constructor field is marked strict, and it is a single-constructor type, then it is possible to ask GHC to unpack the contents of the field directly in its parent with `{-# UNPACK #-}` pragma: - -```haskell -data T1 = T1 {-# UNPACK #-} !(Int, Float) -- => T1 Int Float -data T2 = T2 Double {-# UNPACK #-} !Int -- => T2 Double Int# -``` - -We mention this just because of differences in performance of types we are going to describe now. You don't need to use strict or unboxed types within your work if you don't need to have time/space optimizations and if yes, consider reading [Haskell High Performance Programming](https://github.com/TechBookHunter/Free-Haskell-Books/blob/master/book/Haskell%20High%20Performance%20Programming.pdf). - -### GHC optimization flags - -If you know optimization with GCC, then you won't be surprised how it works with GHC: - -* `-O0` = turn off all optimization -* `-O` or `-O1` = generate good-quality code without taking too long about it -* `-O2` = apply every non-dangerous optimization, even if it means significantly longer compile times (in most cases, there is no significant difference between `-O1` and `-O2`) - -Then there are also `-f*` platform-independent flags, that allows you to turn on and off individual optimizations. For more information, please visit [GHC documentation](http://downloads.haskell.org/~ghc/latest/docs/html/users_guide/using-optimisation.html). - -### Concurrency and Parallelism - -Haskell (of course) supports parallelism or concurrency in order to achieve faster and efficient computation. For parallelism and concurrency visit [wiki.haskell.org/Parallel](https://wiki.haskell.org/Parallel). You can both: - -* run parallel threads with [Control.Parallel](http://hackage.haskell.org/package/parallel/docs/Control-Parallel.html), -* run simultaneous IO actions with forks. - -It is also possible to do distributed computations on clusters but it is far beyond the scope of this course. - -```haskell -import Control.Parallel - -parfib 0 = return 1 -parfib 1 = return 1 -parfib n = do - n1 <- parfib (n - 1) - n2 <- parfib (n - 2) - n3 <- (n1 `par` (n2 `seq` (return (n1 + n2 + 1)))) - return n3 - -main = do x <- parfib 30; print x -``` - -GHC supports running programs in parallel on an SMP (symmetric multiprocessor) or multi-core machine. Just compile your program using the `-threaded` switch and then run it with RTS option `-N ` (where `` is the number of simultaneous threads). See [GHC docs](https://downloads.haskell.org/~ghc/latest/docs/html/users_guide/parallel.html) for more information. - -### FFI - -As with many other programming languages, Haskell supports [FFI (Foreign Function Interface)](https://wiki.haskell.org/Foreign_Function_Interface) that allows co-operating with programs written with other languages. We've already could see that in the example of Haste in DS Wizard where there were some JS bindings. But you can also use it to call some functions from C++ or Rust: - -```cpp -extern "C"{ // need to expose with extern, could use header file .h or .hpp for that - extern int fib(int n) { - if(n < 0) return -1; - int x = 0, y = 1, tmp; - while(n-- > 0) { - tmp = x; - x = y; - y = tmp + x; - } - return x; - } -} -``` - -```haskell -{-# LANGUAGE ForeignFunctionInterface #-} - -import Foreign.C -import System.Environment - -foreign import ccall "fib" cfib :: CInt -> CInt - -main :: IO () -main = do - args <- getArgs - print . cfib . read . head $ args -``` - -``` -% gcc -c -o fib.o fib.cpp -% ghc --make -o ffi_fib FibonacciFFI.hs fib.o -Linking ffi_fib ... -% /usr/bin/time -p ./ffi_fib 25 -75025 -real 0.00 -user 0.00 -sys 0.00 -``` - -Similarly, there is `foreign export` to expose some Haskell functions to other FFIs. Nice example is here: [jarrett/cpphs](https://github.com/jarrett/cpphs). - -## Debugging - -Even if you are a good Haskell programmer, things can go wrong and especially in big projects it is a nontrivial challenge to find out where you did some mistake. Going thru the code in multiple functions, inner functions, various modules, etc. can be painful. Luckilly, there are some ways how to debug Haskell program and some are pretty easy and similar to well-known. - -### Tracing with `Debug.Trace` - -You should already know how to use GHC and GHCi to compile, link and examine Haskell programs. The simplest tool to use for debugging is the `trace` from [Debug.Trace](https://hackage.haskell.org/package/base.0/docs/Debug-Trace.html) which outputs the trace message given as its first argument, before returning the second argument as its result. There are many more *traces* defined for different cases: `traceShow`, `traceId`, `traceStack`, `traceIO`, `traceM`, etc. So you can use it for custom debugging output anywhere in the code. - -For example: - -```haskell -func a b = trace ("func " ++ show a ++ " " ++ show b) undefined -``` - -Or better usage with our example of Fibonacci numbers to see the calls: - -```haskell -module Main where - -import Debug.Trace - --- | Naive recursive algorithm for n-th Fibonacci number -fib1 :: Integer -> Integer -fib1 0 = trace "fib1 0" 0 -fib1 1 = trace "fib1 1" 1 -fib1 n = trace ("fib1 " ++ show n) (fib1 (n-1) + fib1 (n-2)) - --- | Improved recursive algorithm for n-th Fibonacci number -fib2 :: Integer -> Integer -fib2 = fib 0 1 - where - fib x _ 0 = trace "fib2 0" x - fib x y n = trace ("fib2 " ++ show n) (fib y (x+y) (n-1)) - -main :: IO () -main = do - print (fib1 4) - putStrLn "------------" - print (fib2 4) -``` - -``` -% runhaskell FibonacciTrace.hs -fib1 4 -fib1 2 -fib1 0 -fib1 1 -fib1 3 -fib1 1 -fib1 2 -fib1 0 -fib1 1 -3 ------------- -fib2 4 -fib2 3 -fib2 2 -fib2 1 -fib2 0 -3 -``` - -### GHCi debugger - -If you need a better debugger, you can use [GHCi debugger](https://downloads.haskell.org/~ghc/7.4.1/docs/html/users_guide/ghci-debugger.html) (other compilers, such as Hugs, have some different), which allows: - -* setting breakpoints and stepping, -* inspecting variables, -* tracing, -* working with exceptions, -* and so on. - -``` -Prelude> :l FibonacciNaive.hs -[1 of 1] Compiling Main ( FibonacciNaive.hs, interpreted ) -Ok, modules loaded: Main. -*Main> :break 9 -Breakpoint 0 activated at FibonacciNaive.hs:9:10-60 -*Main> fib1 5 -Stopped in Main.fib1, FibonacciNaive.hs:9:10-60 -_result :: Integer = _ -n :: Integer = 5 -[FibonacciNaive.hs:9:10-60] *Main> :continue -fib1 5 -Stopped in Main.fib1, FibonacciNaive.hs:9:10-60 -_result :: Integer = _ -n :: Integer = 3 -[FibonacciNaive.hs:9:28-33] *Main> :show breaks -[0] Main FibonacciNaive.hs:9:10-60 -[FibonacciNaive.hs:9:28-33] *Main> :abandon -*Main> -``` - -### `debug` package - -An interesting solution brings also the [debug](https://hackage.haskell.org/package/debug) package (and related extensions). It uses *Template Haskell* to examine the code and algorithms. - -```haskell -{-# LANGUAGE TemplateHaskell, ViewPatterns, PartialTypeSignatures #-} -{-# OPTIONS_GHC -Wno-partial-type-signatures #-} -module QuickSort(quicksort) where -import Data.List -import Debug - -debug [d| - quicksort :: Ord a => [a] -> [a] - quicksort [] = [] - quicksort (x:xs) = quicksort lt ++ [x] ++ quicksort gt - where (lt, gt) = partition (<= x) xs - |] -``` - -## Further reading - -* [Haskell - Debugging](https://wiki.haskell.org/Debugging) -* [Haskell - Performance](https://wiki.haskell.org/Performance) -* [Haskell - Concurrency](https://wiki.haskell.org/Concurrency) -* [Real World Haskell - Concurrent and Multicore Programming](http://book.realworldhaskell.org/read/concurrent-and-multicore-programming.html) -* [GHC - Concurrent and Parallel Haskell](https://downloads.haskell.org/~ghc/7.0.3/docs/html/users_guide/lang-parallel.html) - diff --git a/tutorials/12_exts-deptypes.md b/tutorials/12_exts-deptypes.md deleted file mode 100644 index b95cbcb..0000000 --- a/tutorials/12_exts-deptypes.md +++ /dev/null @@ -1,254 +0,0 @@ -# GHC Extensions and Dependent Types - -This lecture is a sort of "gourmet" -- the concepts presented here are not used every day nor are common in production, nor are they topics to speak over coffee with mediocre programmers. Rather they represent cutting-edge topics that may potentially write the future of programming. Enjoy ;-) - -## GHC Language Extensions - -Language extensions are used to enable language features in Haskell that may seem useful in certain cases. They can be used to loosen restrictions in the type system or add completely new language constructs to Haskell. As you already know, they can be enabled using the `{-# LANGUAGE #-}` pragma or using flags `-X`. You should always consider using those extensions over normal Haskell, because it may potentially bring some risks. It is advisable to read some discussions on forums. - -### TypeFamilies - -This extension allows use and definition of indexed type and data families to facilitate type-level programming. Indexed type families, or type families for short, are type constructors that represent sets of types. Set members are denoted by supplying the type family constructor with type parameters, which are called type indices. The difference between vanilla parametrized type constructors and family constructors is much like between parametrically polymorphic functions and (ad-hoc polymorphic) methods of type classes. Parametric polymorphic functions behave the same in all type instances, whereas class methods can change their behaviour in dependence on the class type parameters. Similarly, vanilla type constructors imply the same data representation for all type instances, but family constructors can have varying representation types for varying type indices. (see [GHC docs](https://downloads.haskell.org/~ghc/latest/docs/html/users_guide/glasgow_exts.html#type-families)) - -```haskell -{-# LANGUAGE TypeFamilies #-} - --- Declare a list-like data family -data family XList a - --- Declare a list-like instance for Char -data instance XList Char = XCons !Char !(XList Char) | XNil - --- Declare a number-like instance for () -data instance XList () = XListUnit !Int - -class XLength a where - xlength :: XList a -> Int - -instance XLength Char where - xlength XNil = 0 - xlength (XCons _ r) = 1 + xlength r - -instance XLength () where - xlength (XListUnit _) = 1 -``` - -### GADTs - -[Generalized algebraic data type](https://en.wikipedia.org/wiki/Generalized_algebraic_data_type) are a generalization of the algebraic data types that you are familiar with. Basically, they allow you to explicitly write down the types of the constructors. In this chapter, you'll learn why this is useful and how to declare your own. GADTs are mainly used to implement domain-specific languages, and so this section will introduce them with a corresponding example. - -```haskell -{-# LANGUAGE GADTs #-} - -data Expr a where - I :: Int -> Expr Int - B :: Bool -> Expr Bool - Add :: Expr Int -> Expr Int -> Expr Int - Mul :: Expr Int -> Expr Int -> Expr Int - Eq :: Expr Int -> Expr Int -> Expr Bool - And :: Expr Bool -> Expr Bool -> Expr Bool - -eval :: Expr a -> a -eval (I n) = n -- return Int -eval (B b) = b -- returns bool -eval (Add e1 e2) = eval e1 + eval e2 -- return Int -eval (Mul e1 e2) = eval e1 * eval e2 -- return Int -eval (Eq e1 e2) = eval e1 == eval e2 -- returns bool -eval (And e1 e2) = eval e1 && eval e2 -- returns bool -``` - -Complete example: [Haskell - GADT](https://en.wikibooks.org/wiki/Haskell/GADT) - -### QuasiQuotes - -[Quasiquoting](https://wiki.haskell.org/Quasiquotation) allows programmers to use custom, domain-specific syntax to construct fragments of their program. Along with Haskell's existing support for domain specific languages, you are now free to use new syntactic forms for your EDSLs. We've already seen it used in Yesod or Debug. Another simple use is with [Text.RawString.QQ](http://hackage.haskell.org/package/raw-strings-qq/docs/Text-RawString-QQ.html) to allow multiline strings: - -```haskell -{-# LANGUAGE QuasiQuotes #-} -import Text.RawString.QQ - -multiline :: String -multiline = [r| - -Auto-generated html formated source - - - -

-
|]
-```
-
-You can, of course, write your own DSL or simplify the syntax for yourself. All you have to do is implement your [QuasiQuoter](http://hackage.haskell.org/package/template-haskell/docs/Language-Haskell-TH-Quote.html#t:QuasiQuoter) (part of Template Haskell). For example, you can create a simple string-string map with semicolon and newlines:
-
-```haskell
-{-# LANGUAGE TemplateHaskell #-}
-module MapBuilder (mapBuilder) where
-
-import Language.Haskell.TH
-import Language.Haskell.TH.Quote
-
--- | Map Builder quasiquoter
-mapBuilder = QuasiQuoter
-    { quoteExp  = mapBuilderParser -- expressions
-    , quotePat  = undefined        -- patterns
-    , quoteType = undefined        -- types
-    , quoteDec  = undefined        -- declarations
-    }
-
--- | Split string to two parts by given char
-splitByFirst :: String -> Char -> (String, String)
-splitByFirst str sep = splitByFirst' "" str
-  where
-    splitByFirst' a [] = (a, "")
-    splitByFirst' a (x:xs)
-       | x == sep  = (a, xs)
-       | otherwise = splitByFirst' (a++[x]) xs
-
--- | Trim spaces and tabs from left of the string
-trimLeft :: String -> String
-trimLeft "" = ""
-trimLeft (x:xs)
-  | x `elem` [' ', '\t'] = trimLeft xs
-  | otherwise        = x : trimLeft xs
-
--- | Parse [(String, String)] map from String
-mapBuilderParser :: String -> Q Exp
-mapBuilderParser = return . ListE . map parseTuples . filter (/="") . map trimLeft . lines
-  where
-    parseTuples :: String -> Exp
-    parseTuples xs = TupE [LitE . StringL $ key, LitE . StringL $ val]
-      where
-        parts = splitByFirst xs ':'
-        key = fst parts
-        val = snd parts
-```
-
-Then you can simply import defined quasiquoter and use it:
-
-```haskell
-{-# LANGUAGE QuasiQuotes #-}
-
-import MapBuilder
-
-mymap1 :: [(String, String)]
-mymap1 = [mapBuilder|
-           a:10
-           b:22
-           c:hello
-           it:has:no:problem
-         |]
-
-mymap2 :: [(String, Int)]
-mymap2 = map strstr2strint [mapBuilder|
-           suchama4:1210
-           perglr:1535
-         |]
-
-strstr2strint :: (String, String) -> (String, Int)
-strstr2strint (x, y) = (x, read y)
-```
-
-Beautiful, right?!
-
-### Template Haskell
-
-[Template Haskell](http://hackage.haskell.org/package/template-haskell) is a GHC extension to Haskell that adds compile-time metaprogramming facilities. The original design can be found here: http://research.microsoft.com/en-us/um/people/simonpj/papers/meta-haskell/. You could have seen part of it in action in the previous section about quasiquoting but it can do much more although quasiquotes are an important part of it. Great explanation is [here](https://ocharles.org.uk/blog/guest-posts/2014-12-22-template-haskell.html) and [here](https://markkarpov.com/tutorial/th.html).
-
-## Dependent and Refinement Types
-
-A dependent type is a type whose definition depends on a value. Such types can be for example:
-
-* pair of integers where the second is greater than the first,
-* people with age between 18 and 65,
-* string in email format (matches given regex).
-
-Dependent types add complexity to a type system. Deciding the equality of dependent types in a program may require computations. If arbitrary values are allowed in dependent types, then deciding type equality may involve deciding whether two arbitrary programs produce the same result; hence type checking may become undecidable.
-
-### Agda
-
-[Agda](http://wiki.portal.chalmers.se/agda/pmwiki.php) is a dependently typed functional programming language originally developed by Ulf Norell at Chalmers University of Technology with the implementation described in his PhD thesis. But current version, Agda 2, is a completely rewritten previous Agda from 1999.
-
-Visit https://github.com/agda/agda where you find some examples as well!
-
-### Idris
-
-[Idris](https://www.idris-lang.org) is a general-purpose purely functional programming language with dependent types, strict or optional lazy evaluation and features such as a totality checker. Idris is highly affected by Haskell and Agda which is visible in its syntax.
-
-Its features are influenced by Haskell and ML, and include:
-
-* Full dependent types with dependent pattern matching
-* Simple foreign function interface (to C)
-* Compiler-supported interactive editing: the compiler helps you write code using the types
-where clauses, with a rule, simple case expressions, pattern matching let and lambda bindings
-* Dependent records with projection and update
-* Interfaces (similar to type classes in Haskell)
-* Type-driven overloading resolution
-* `do` notation and idiom brackets
-* Indentation significant syntax
-* Extensible syntax
-* Cumulative universes
-* Totality checking
-* Hugs-style interactive environment
-
-On their website, you can find a documentation with [examples](https://www.idris-lang.org/example/) such as Vectors:
-
-```idris
-infixr 5 ::
-
-data Vect : Nat -> Type -> Type where
-    Nil  : Vect Z a
-    (::) : a -> Vect k a -> Vect (S k) a
-
-app : Vect n a -> Vect m a -> Vect (n + m) a
-app Nil       ys = ys
-app (x :: xs) ys = x :: app xs ys
-```
-
-### LiquidHaskell
-
-LiquidHaskell is a static verifier for Haskell, based on Liquid Types. It allows annotating code with invariants that complement the invariants imposed by the types. These invariants are checked with a SMT solver. It is not about dependent types but [refinement types](https://en.wikipedia.org/wiki/Refinement_(computing)#Refinement_types) (you refine some defined type with rules, not build it dependent from the scratch).
-
-Visit: https://ucsd-progsys.github.io/liquidhaskell-blog/
-
-```haskell
-{--! run liquid with no-termination -}
-
-module SimpleRefinements where
-import Prelude hiding ((!!), length)
-import Language.Haskell.Liquid.Prelude
-
-
--- |Simple Refinement Types
-
-{-@ zero :: {v:Int | v = 0} @-}
-zero     :: Int
-zero     =  0
-
-{-@ type Even = {v:Int | v mod 2 = 0} @-}
-
-{-@ zero'' :: Even @-}
-zero''     :: Int
-zero''     =  0
-
--- |Lists
-
-infixr `C`
-data L a = N | C a (L a)
-
-{-@ natList :: L Nat @-}
-natList     :: L Int
-natList     =  0 `C` 1 `C` 3 `C` N
-
-{-@ evenList :: L Even @-}
-evenList     :: L Int
-evenList     =  0 `C` 2 `C` 8 `C` N
-```
-
-## Further reading
-
-* [Haskell Wiki - Language extensions](https://wiki.haskell.org/Language_extensions)
-* [24 Days of GHC Extensions](https://ocharles.org.uk/blog/pages/2014-12-01-24-days-of-ghc-extensions.html)
-* [Agda](https://github.com/agda/agda)
-* [Idris](https://www.idris-lang.org)
-* [Idris - tutorial](http://docs.idris-lang.org/en/latest/tutorial/)
-* [LiquidHaskell](https://ucsd-progsys.github.io/liquidhaskell-blog/)
diff --git a/tutorials/images/tea.png b/tutorials/images/tea.png
new file mode 100644
index 0000000..4b58d5b
Binary files /dev/null and b/tutorials/images/tea.png differ