├── .github └── workflows │ └── gh-pages.yml ├── .gitignore ├── LICENSE.txt ├── assets ├── book-logo-transparent.png ├── book-logo.png ├── cover.png └── pdf.png ├── book.toml ├── lhbg-v0.pdf ├── readme.md └── src ├── 01-about.md ├── 02-hello.md ├── 03-html ├── 01-html_content.md ├── 02-type_signatures.md ├── 03-edsls.md ├── 04-safer_construction.md ├── 05-modules.md ├── 06-escaping_characters.md ├── 07-internal_modules.md ├── 08-exercises.md └── 09-summary.md ├── 03-html_printer.md ├── 04-markup.md ├── 04-markup ├── 01-data_type.md ├── 02-parsing_01.md ├── 03-displaying_results.md └── 04-parsing_02.md ├── 05-glue.md ├── 05-glue ├── 01-markup_to_html.md ├── 02-io.md ├── 03-project.md └── 04-optparse.md ├── 06-errors_and_files.md ├── 06-errors_and_files ├── 01-either.md ├── 02-except.md ├── 03-exceptions.md ├── 04-implementation.md └── 05-summary.md ├── 07-environment.md ├── 08-testing.md ├── 09-documentation.md ├── 10-recap.md ├── 11-next.md ├── 12-faq.md └── SUMMARY.md /.github/workflows/gh-pages.yml: -------------------------------------------------------------------------------- 1 | name: github pages 2 | 3 | on: 4 | push: 5 | branches: 6 | - book 7 | pull_request: 8 | 9 | jobs: 10 | deploy: 11 | runs-on: ubuntu-20.04 12 | steps: 13 | - uses: actions/checkout@v2 14 | 15 | - name: Setup mdBook 16 | shell: bash 17 | run: | 18 | wget https://gilmi.me/static/misc/mymdbook2 19 | chmod +x mymdbook2 20 | ./mymdbook2 build 21 | cp assets/book-logo.png book/ 22 | cp assets/book-logo-transparent.png book/ 23 | cp assets/pdf.png book/ 24 | cp lhbg-v0.pdf book/ 25 | echo "learn-haskell.blog" > book/CNAME 26 | 27 | - name: Deploy 28 | uses: peaceiris/actions-gh-pages@v3 29 | if: github.ref == 'refs/heads/book' 30 | with: 31 | github_token: ${{ secrets.GITHUB_TOKEN }} 32 | publish_dir: ./book 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .stack-work/ 2 | *~ 3 | book 4 | output/ 5 | -------------------------------------------------------------------------------- /assets/book-logo-transparent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/soupi/learn-haskell-blog-generator/8e85924aa30ff90a8b2446340ca29652ea062e22/assets/book-logo-transparent.png -------------------------------------------------------------------------------- /assets/book-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/soupi/learn-haskell-blog-generator/8e85924aa30ff90a8b2446340ca29652ea062e22/assets/book-logo.png -------------------------------------------------------------------------------- /assets/cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/soupi/learn-haskell-blog-generator/8e85924aa30ff90a8b2446340ca29652ea062e22/assets/cover.png -------------------------------------------------------------------------------- /assets/pdf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/soupi/learn-haskell-blog-generator/8e85924aa30ff90a8b2446340ca29652ea062e22/assets/pdf.png -------------------------------------------------------------------------------- /book.toml: -------------------------------------------------------------------------------- 1 | [book] 2 | title = "Learn Haskell by building a blog generator" 3 | description = "A project-oriented online book about Haskell" 4 | authors = ["Gil Mizrahi"] 5 | language = "en" 6 | src = "src" 7 | 8 | [output.html] 9 | git-repository-url = "https://github.com/soupi/learn-haskell-blog-generator" 10 | git-repository-icon = "fa-github" 11 | default-theme = "light" 12 | -------------------------------------------------------------------------------- /lhbg-v0.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/soupi/learn-haskell-blog-generator/8e85924aa30ff90a8b2446340ca29652ea062e22/lhbg-v0.pdf -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # learn-haskell-blog-generator 2 | 3 | This is the source repository for 4 | "Learn Haskell by building a blog generator", 5 | a project-oriented book about Haskell. 6 | 7 | To view the book, visit the [website](https://learn-haskell.blog). 8 | 9 | To report issues and ask questions, 10 | visit the [issue tracker](https://github.com/soupi/learn-haskell-blog-generator/issues). 11 | 12 | ## Code reference 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 |
ChapterCodeCommit
3.9 - HTML EDSLTree
4.4 - Markup parsingTreeDiff
5.1 - Markup to HTML conversionTreeDiff
5.2 - IOTreeDiff
5.3 - Package formatTreeDiff
5.4 - CLI options parsingTreeDiff
6 - Richer HTML contentTreeDiff
6.5 - Handling multiple filesTreeDiff
7 - EnvironmentTreeDiff
8 - TestingTreeDiff
75 | -------------------------------------------------------------------------------- /src/01-about.md: -------------------------------------------------------------------------------- 1 | # About this book 2 | 3 | >

Looking for reviews and mentions? Click here.

4 | 5 | 10 | 11 | 16 | 17 | In this book, we will implement a simple static blog generator in Haskell, 18 | converting documents written in our own custom markup language to HTML. 19 | 20 | We will: 21 | 22 | 1. Implement a tiny HTML printer library 23 | 2. Define and parse our own custom markup language 24 | 3. Read files and glue things together 25 | 4. Add command line arguments parsing 26 | 5. Write tests and documentation 27 | 28 | In each chapter of the book, we will focus on a particular task we wish to achieve, 29 | and throughout the chapter, learn just enough Haskell to complete the task. 30 | 31 | ## Other ways to read this book 32 | 33 |
34 | 35 |
36 | 37 | 38 | 39 |

Do you prefer watching videos?
40 | Impure Pics made a video series based on this book! 41 |

42 |
43 | 44 |
45 | 46 | 47 | 48 |

Do you prefer reading PDFs?
49 | An experimental PDF version is now available.

50 |
51 | 52 |
53 | 54 | ## Why should you read this book? 55 | 56 | There are many Haskell tutorials, guides, and books out there. Why read this one? 57 | 58 | ### Pros 59 | 60 | There are probably more, but here are a few possible pros: 61 | 62 | - It is **relatively short** - most Haskell books are hundreds of pages long. 63 | This book (when exported to PDF) is roughly 150 pages long. 64 | - It is **project oriented**. Many Haskell books teach Haskell by teaching the underlying 65 | concepts and features in a neat progression. In this book, we **build a Haskell program** 66 | and learn Haskell on the way. This will be a pro to some and a con to others.
67 | There are other tutorials like this. The most notable ones are 68 | [Beginning Haskell](https://www.apress.com/gp/book/9781430262510#otherversion=9781430262503) 69 | and [Haskell via Sokoban](https://haskell-via-sokoban.nomeata.de/). 70 | - It touches on **important topics** such as design patterns, testing, and documentation. 71 | - It has **many exercises** as well as **solutions** to those exercises. 72 | - It's **online**, which means corrections are easy to make. 73 | - It's **free**. 74 | 75 | ### Cons 76 | 77 | There are probably more, but here are a few possible cons: 78 | 79 | - It **may lack depth** - many, much longer Haskell tutorials are long because they go 80 | deeper into the nuts and bolts of each feature, and I tried to keep this book relatively short. 81 | - It **may not cover as many features or techniques** as other tutorials - 82 | we try to cover features as they pop up in our implementation, but we will 83 | probably miss features that aren't as important for our tasks, 84 | while other resources may try to cover many different use cases. 85 | - It **does not have a technical editor**, though it has seen quite a bit of editing. 86 | 87 | ### Other learning resources 88 | 89 | The [haskell.org/documentation](https://www.haskell.org/documentation/) page lists 90 | many tutorials, books, guides, and courses. You can find a few alternatives that I can 91 | recommend [in this list](https://github.com/soupi/haskell-study-plan#about-this-guide). 92 | 93 | 102 | 103 | ## Discussions 104 | 105 | Do you want to discuss the book? Maybe ask a question? 106 | Try the [discussion board](https://github.com/soupi/learn-haskell-blog-generator/discussions)! 107 | 108 | -------------------------------------------------------------------------------- /src/02-hello.md: -------------------------------------------------------------------------------- 1 | # Hello, world! 2 | 3 | In this chapter, we will create a simple HTML "hello world" program and use the Haskell toolchain 4 | to compile and run it. 5 | 6 | > If you haven't installed a Haskell toolchain yet, visit 7 | > [haskell.org/downloads](https://haskell.org/downloads) for instructions on how to download 8 | > and install a Haskell toolchain. 9 | 10 | ## A Haskell source file 11 | 12 | A Haskell source file is composed of definitions. 13 | 14 | The most common type of definition has the following form: 15 | 16 | ```hs 17 | = 18 | ``` 19 | 20 | Note that: 21 | 22 | 1. Names must start with a lowercase letter 23 | 2. We cannot use the same name more than once in a file 24 | 25 | A source file containing a definition of the name `main` can be treated as an executable, 26 | and the expression `main` is bound to is the entry point to the program. 27 | 28 | Let's create a new Haskell source file called `hello.hs` and write the following line there: 29 | 30 | ```hs 31 | main = putStrLn "Hello, world!" 32 | ``` 33 | 34 | We've defined a new name, `main`, and bound it to the expression `putStrLn "Hello, world!"`. 35 | 36 | the body of `main` means calling the function `putStrLn` with the string `"Hello, world!"` 37 | as input. `putStrLn` takes a single string as input and prints that string to the standard output. 38 | 39 | __Note__: we don't need parenthesis to pass arguments to functions in Haskell. 40 | 41 | Running this program will result in the following text printed on the screen: 42 | 43 | ``` 44 | Hello, world! 45 | ``` 46 | 47 | Note that we cannot just write `putStrLn "Hello, world!"` 48 | without the `main =` part, because it is not a definition. This is something that is allowed 49 | in languages such as Python and OCaml, but not in Haskell or, for example, C. 50 | 51 | ## Compiling programs 52 | 53 | To run this little program, we can compile it using the command line program `ghc`: 54 | 55 | ```sh 56 | > ghc hello.hs 57 | [1 of 1] Compiling Main ( hello.hs, hello.o ) 58 | Linking hello ... 59 | ``` 60 | 61 | Invoking `ghc` with `hello.hs` will create the following artifact files: 62 | 63 | 1. `hello.o` - Object file 64 | 2. `hello.hi` - Haskell interface file 65 | 3. `hello` - A native executable file 66 | 67 | And after the compilation, we can run the `hello` executable: 68 | 69 | ```sh 70 | > ./hello 71 | Hello, world! 72 | ``` 73 | 74 | ## Interpreting programs 75 | 76 | Alternatively, we can skip the compilation and creation of artifact files phase and run the source file directly 77 | using the command line program `runghc`: 78 | 79 | ```sh 80 | > runghc hello.hs 81 | Hello, world! 82 | ``` 83 | 84 | We can also redirect the output of the program to a file and then open it in Firefox. 85 | 86 | ```sh 87 | > runghc hello.hs > hello.html 88 | > firefox hello.html 89 | ``` 90 | 91 | This command should open Firefox and display a web page with `Hello, world!` written in it. 92 | 93 | I recommend using `runghc` with this tutorial. While compiling produces significantly faster programs, 94 | interpreting programs provides us with faster feedback while we are developing and making frequent changes. 95 | 96 | > If you want to learn more about the core Haskell tools, you can read 97 | > [this article](https://gilmi.me/blog/post/2021/08/14/hs-core-tools), 98 | > but what's described above is enough for our usage at the moment. 99 | 100 | ## More bindings 101 | 102 | We can define the HTML string passed to `putStrLn` in a new name instead of passing 103 | it directly to `putStrLn`. Change the content of the `hello.hs` file we defined above to: 104 | 105 | ```hs 106 | main = putStrLn myhtml 107 | 108 | myhtml = "Hello, world!" 109 | ``` 110 | 111 | __Note__: the order in which we declare the bindings does not matter. 112 | -------------------------------------------------------------------------------- /src/03-html/01-html_content.md: -------------------------------------------------------------------------------- 1 | # Flexible HTML content (functions) 2 | 3 | We'd like to be able to write different HTML pages without having to write the whole 4 | structure of HTML and body tags over and over again. We can do that with functions. 5 | 6 | To define a function, we create a definition as we saw previously and add the argument 7 | names after the name and before the equals sign (`=`). 8 | So a function definition has the following form: 9 | 10 | ```hs 11 | ... = 12 | ``` 13 | 14 | The argument names will be available in scope on the right side of the equals sign 15 | (in the ``), and the function name will be ``. 16 | 17 | We'll define a function that takes a string, which is the content of the page, and wraps it in 18 | the relevant `html` and `body` tags by concatenating them before and after the content. 19 | We use the operator `<>` to concatenate two strings. 20 | 21 | ```hs 22 | wrapHtml content = "" <> content <> "" 23 | ``` 24 | 25 | This function, `wrapHtml`, takes one argument named `content` and returns a string 26 | that prefixes `` before the content and appends `` after it. 27 | Note that it is common to use camelCase in Haskell for names. 28 | 29 | Now we can adjust our `myhtml` definition from the previous chapter: 30 | 31 | ```hs 32 | myhtml = wrapHtml "Hello, world!" 33 | ``` 34 | 35 | Again, notice that we don't need parenthesis when calling functions. Function calls have the form: 36 | 37 | ```hs 38 | ... 39 | ``` 40 | 41 | However, if we wanted to substitute `myhtml` with the expression `myhtml` is bound 42 | to in `main = putStrLn myhtml`, we would have to wrap the expression in parenthesis: 43 | 44 | ```hs 45 | main = putStrLn (wrapHtml "Hello, world!") 46 | ``` 47 | 48 | If we accidentally write this instead: 49 | 50 | ```hs 51 | main = putStrLn wrapHtml "Hello, world!" 52 | ``` 53 | 54 | 55 | we'll get an error from GHC stating that `putStrLn` is applied to two arguments, 56 | but it only takes one. This is because the above is of the form ` ` 57 | in which, as we defined earlier, `` and `` are arguments to ``. 58 | 59 | Using parenthesis, we can group the expressions together in the correct order. 60 | 61 | > #### An aside about operator precedence and fixity 62 | > 63 | > operators (like `<>`) are infix functions that take two arguments - one from each side. 64 | > 65 | > When there are multiple operators in the same expression without parenthesis, the operator 66 | > *fixity* (left or right) and *precedence* (a number between 0 and 10) determine which 67 | > operator binds more tightly. 68 | > 69 | > In our case, `<>` has *right* fixity, so Haskell adds an invisible parenthesis on the right side 70 | > of `<>`. So, for example: 71 | > 72 | > ```hs 73 | > "" <> content <> "" 74 | > ``` 75 | > 76 | > is viewed by Haskell as: 77 | > 78 | > ```hs 79 | > "" <> (content <> "") 80 | > ``` 81 | > 82 | > For an example of precedence, in the expression `1 + 2 * 3`, 83 | > the operator `+` has precedence 6, and the operator `*` has precedence 7, 84 | > so we give precedence to `*` over `+`. Haskell will view this expression as: 85 | > 86 | > ```hs 87 | > 1 + (2 * 3) 88 | > ``` 89 | > 90 | > You might run into errors when mixing different operators with the *same precedence* 91 | > but *different fixity*, because Haskell won't understand how to group these expressions. 92 | > In that case, we can solve the problem by adding parenthesis explicitly. 93 | 94 | --- 95 | 96 | Exercises: 97 | 98 | 1. Separate the functionality of `wrapHtml` into two functions: 99 | 1. One that wraps content in `html` tag 100 | 2. one that wraps content in a `body` tag 101 | 102 | Name the new functions `html_` and `body_`. 103 | 2. Change `myhtml` to use these two functions. 104 | 3. Add another two similar functions for the tags `` and `` 105 | and name them `head_` and `title_`. 106 | 4. Create a new function, `makeHtml`, which takes two strings as input: 107 | 1. One string for the title 108 | 2. One string for the body content 109 | 110 | And construct an HTML string using the functions implemented in the previous exercises. 111 | 112 | The output for: 113 | 114 | ```hs 115 | makeHtml "My page title" "My page content" 116 | ``` 117 | 118 | should be: 119 | 120 | ```html 121 | <html><head><title>My page titleMy page content 122 | ``` 123 | 5. Use `makeHtml` in `myhtml` instead of using `html_` and `body_` directly 124 | 125 | --- 126 | 127 | Solutions: 128 | 129 |
130 | Solution for exercise #1 131 | 132 | ```hs 133 | html_ content = "" <> content <> "" 134 | 135 | body_ content = "" <> content <> "" 136 | ``` 137 | 138 |
139 | 140 |
141 | Solution for exercise #2 142 | 143 | ```hs 144 | myhtml = html_ (body_ "Hello, world!") 145 | ``` 146 | 147 |
148 | 149 |
150 | Solution for exercise #3 151 | 152 | ```hs 153 | head_ content = "" <> content <> "" 154 | 155 | title_ content = "" <> content <> "" 156 | ``` 157 | 158 |
159 | 160 |
161 | Solution for exercise #4 162 | 163 | ```hs 164 | makeHtml title content = html_ (head_ (title_ title) <> body_ content) 165 | ``` 166 | 167 |
168 | 169 | 170 |
171 | Solution for exercise #5 172 | 173 | ```hs 174 | myhtml = makeHtml "Hello title" "Hello, world!" 175 | ``` 176 | 177 |
178 | 179 | 180 |
181 | Our final program 182 | 183 | ```hs 184 | -- hello.hs 185 | 186 | main = putStrLn myhtml 187 | 188 | myhtml = makeHtml "Hello title" "Hello, world!" 189 | 190 | makeHtml title content = html_ (head_ (title_ title) <> body_ content) 191 | 192 | html_ content = "" <> content <> "" 193 | 194 | body_ content = "" <> content <> "" 195 | 196 | head_ content = "" <> content <> "" 197 | 198 | title_ content = "" <> content <> "" 199 | ``` 200 | 201 | We can now run our `hello.hs` program, pipeline the output into a file, 202 | and open it in our browser: 203 | 204 | ```sh 205 | runghc hello.hs > hello.html 206 | firefox hello.html 207 | ``` 208 | 209 | It should display `Hello, world!` on the page and `Hello title` on the page's title. 210 | 211 |
212 | 213 | 214 | --- 215 | 216 | ## Indentation 217 | 218 | You might ask how does Haskell know a definition is complete? 219 | The answer is: Haskell uses indentation to know when things should be grouped together. 220 | 221 | Indentation in Haskell can be a bit tricky, but in general: code that is supposed to be 222 | part of some expression should be indented further than the beginning of that expression. 223 | 224 | We know the two definitions are separate because the second one is not indented further than the first one. 225 | 226 | 227 | ### Indentation tips 228 | 229 | 1. Choose a specific amount of spaces for indentation (2 spaces, 4 spaces, etc.) and stick to it. 230 | Always use spaces over tabs. 231 | 2. Do not indent more than once at any given time. 232 | 3. When in doubt, drop the line as needed and indent once. 233 | 234 | Here are a few examples: 235 | 236 | ```hs 237 | main = 238 | putStrLn "Hello, world!" 239 | ``` 240 | 241 | or: 242 | 243 | ```hs 244 | main = 245 | putStrLn 246 | (wrapHtml "Hello, world!") 247 | ``` 248 | 249 | __Avoid the following styles__, which use more than one indentation step, or completely disregard 250 | indentation steps: 251 | 252 | ```hs 253 | main = putStrLn 254 | (wrapHtml "Hello, world!") 255 | ``` 256 | 257 | ```hs 258 | main = putStrLn 259 | (wrapHtml "Hello, world!") 260 | ``` 261 | 262 | -------------------------------------------------------------------------------- /src/03-html/02-type_signatures.md: -------------------------------------------------------------------------------- 1 | # Adding type signatures 2 | 3 | Haskell is a **statically typed** programming language. That means that every 4 | expression has a type, and we check that the types are valid with 5 | regard to each other before running the program. If we discover that 6 | they are not valid, an error message will be printed, and the program 7 | will not run. 8 | 9 | An example of a type error would be if we'd pass 3 arguments to a function 10 | that takes only 2, or pass a number instead of a string. 11 | 12 | Haskell is also **type inferred**, so we don't *need* to specify the type 13 | of expressions - Haskell can *infer* from the context of the expression 14 | what its type should be, and that's what we have done until now. However, **specifying 15 | types is useful** - it adds a layer of documentation for you or others 16 | that will look at the code later, and it helps verify to some degree 17 | that what was intended (with the type signature) is what was 18 | written (with the expression). It is generally recommended to annotate all *top-level* 19 | definitions with type signatures. 20 | 21 | We use a double-colon (`::`) to specify the type of names. We usually 22 | write it right above the definition of the name itself. 23 | 24 | Here are a few examples of types we can write: 25 | 26 | - `Int` - The type of integer numbers 27 | - `String` - The type of strings 28 | - `Bool` - The type of booleans 29 | - `()` - The type of the expression `()`, also called unit 30 | - `a -> b` - The type of a function from an expression of type `a` to an expression of type `b` 31 | - `IO ()` - The type of an expression that represents an IO subroutine that returns `()` 32 | 33 | Let's specify the type of `title_`: 34 | 35 | ```hs 36 | title_ :: String -> String 37 | ``` 38 | 39 | We can see in the code that the type of `title_` is a function that takes 40 | a `String` and returns a `String`. 41 | 42 | Let's also specify the type of `makeHtml`: 43 | 44 | ```hs 45 | makeHtml :: String -> String -> String 46 | ``` 47 | 48 | Previously, we thought about `makeHtml` as a function that takes 49 | two strings and returns a string. 50 | 51 | But actually, all functions in Haskell take **exactly one argument** as input 52 | and return **exactly one value** as output. It's just convenient to refer 53 | to functions like `makeHtml` as functions with multiple inputs. 54 | 55 | In our case, `makeHtml` is a function that takes **one** string argument 56 | and returns a **function**. _The function it returns_ takes a string argument 57 | as well and finally returns a string. 58 | 59 | The magic here is that `->` is right-associative. This means that when we write: 60 | 61 | ```hs 62 | makeHtml :: String -> String -> String 63 | ``` 64 | 65 | Haskell parses it as: 66 | 67 | ```hs 68 | makeHtml :: String -> (String -> String) 69 | ``` 70 | 71 | Consequently, the expression `makeHtml "My title"` is also a function! 72 | One that takes a string (the content, the second argument of `makeHtml`) 73 | and returns the expected HTML string with "My title" in the title. 74 | 75 | This is called **partial application**. 76 | 77 | To illustrate, let's define `html_` and `body_` in a different way by 78 | defining a new function, `el`. 79 | 80 | ```hs 81 | el :: String -> String -> String 82 | el tag content = 83 | "<" <> tag <> ">" <> content <> " tag <> ">" 84 | ``` 85 | 86 | el is a function that takes a tag and content, and wraps the content 87 | with the tag. 88 | 89 | We can now implement `html_` and `body_` by partially applying `el` and 90 | only provide the tag. 91 | 92 | ```hs 93 | html_ :: String -> String 94 | html_ = el "html" 95 | 96 | body_ :: String -> String 97 | body_ = el "body" 98 | ``` 99 | 100 | Note that we didn't need to add the argument on the left side of 101 | equals sign because Haskell functions are "**first class**" - they behave 102 | exactly like values of primitive types like `Int` or `String`. 103 | We can name a function like any other value, 104 | put it in data structures, pass it to functions, and so on! 105 | 106 | The way Haskell treats names is very similar to copy-paste. Anywhere 107 | you see `html_` in the code, you can replace it with `el "html"`. They are 108 | the same (this is what the equals signs say, right? That the two sides 109 | are the same). This property of being able to *substitute* the two sides of the 110 | equals sign with one another is called **referential transparency**. And 111 | it is pretty unique to Haskell (and a few similar languages such as PureScript and Elm)! 112 | We'll talk more about referential transparency in a later chapter. 113 | 114 | ### Anonymous/lambda functions 115 | 116 | To further drive the point that Haskell functions are first class and 117 | all functions take exactly one argument, 118 | I'll mention that the syntax we've been using up until 119 | now to define function is just syntactic sugar! We can also define 120 | **anonymous functions** - functions without a name, anywhere we'd like. 121 | Anonymous functions are also known as **lambda functions** 122 | as a tribute to the formal mathematical system 123 | which is at the heart of all functional programming 124 | languages - the lambda calculus. 125 | 126 | We can create an anonymous function anywhere we'd expect an expression, 127 | such as `"hello"`, using the following syntax: 128 | 129 | ```hs 130 | \ -> 131 | ``` 132 | 133 | This little `\` (which bears some resemblance to the lowercase Greek letter lambda 'λ') 134 | marks the head of the lambda function, 135 | and the arrow (`->`) marks the beginning of the function's body. 136 | We can even chain lambda functions, making them "multiple argument functions" by 137 | defining another lambda in the body of another, like this: 138 | 139 | ```hs 140 | three = (\num1 -> \num2 -> num1 + num2) 1 2 141 | ``` 142 | 143 | As before, we evaluate functions by substituting the function argument with 144 | the applied value. In the example above, we substitute `num1` with `1` and get 145 | `(\num2 -> 1 + num2) 2`. Then substitute `num2` with `2` and get `1 + 2`. 146 | We'll talk more about substitution later. 147 | 148 | So, when we write: 149 | 150 | ```hs 151 | el :: String -> String -> String 152 | el tag content = 153 | "<" <> tag <> ">" <> content <> " tag <> ">" 154 | ``` 155 | 156 | Haskell actually translates this under the hood to: 157 | 158 | ```hs 159 | el :: String -> (String -> String) 160 | el = \tag -> \content -> 161 | "<" <> tag <> ">" <> content <> " tag <> ">" 162 | ``` 163 | 164 | Hopefully, this form makes it a bit clearer why Haskell functions 165 | always take one argument, even when we have syntactic sugar that 166 | might suggest otherwise. 167 | 168 | I'll mention one more syntactic sugar for anonymous functions: 169 | We don't actually have to write multiple argument anonymous functions 170 | this way, we can write: 171 | 172 | ```hs 173 | \ ... -> 174 | ``` 175 | 176 | to save us some trouble. For example: 177 | 178 | ```hs 179 | three = (\num1 num2 -> num1 + num2) 1 2 180 | ``` 181 | 182 | But it's worth remembering what they are under the hood. 183 | 184 | We won't be needing anonymous/lambda functions at this point, 185 | but we'll discuss them later and see where they can be useful. 186 | 187 | --- 188 | 189 | Exercises: 190 | 191 | 1. Add types for all of the functions we created until now 192 | 193 | 2. Change the implementation of the HTML functions we built to use `el` instead 194 | 195 | 3. Add a couple more functions for defining paragraphs and headings: 196 | 1. `p_` which uses the tag `

` for paragraphs 197 | 2. `h1_` which uses the tag `

` for headings 198 | 199 | 4. Replace our `Hello, world!` string with richer content, use `h1_` and `p_`. 200 | We can append HTML strings created by `h1_` and `p_` using the append operator `<>`. 201 | 202 | Bonus: rewrite a couple of functions using lambda functions, just for fun! 203 | 204 | --- 205 | 206 | Solutions: 207 | 208 |
209 | Solution for exercise #1 210 | 211 | ```hs 212 | myhtml :: String 213 | myhtml = makeHtml "Hello title" "Hello, world!" 214 | 215 | makeHtml :: String -> String -> String 216 | makeHtml title content = html_ (head_ (title_ title) <> body_ content) 217 | 218 | html_ :: String -> String 219 | html_ content = "" <> content <> "" 220 | 221 | body_ :: String -> String 222 | body_ content = "" <> content <> "" 223 | 224 | head_ :: String -> String 225 | head_ content = "" <> content <> "" 226 | 227 | title_ :: String -> String 228 | title_ content = "" <> content <> "" 229 | ``` 230 | 231 |
232 | 233 |
234 | Solution for exercise #2 235 | 236 | ```hs 237 | html_ :: String -> String 238 | html_ = el "html" 239 | 240 | body_ :: String -> String 241 | body_ = el "body" 242 | 243 | head_ :: String -> String 244 | head_ = el "head" 245 | 246 | title_ :: String -> String 247 | title_ = el "title" 248 | ``` 249 | 250 |
251 | 252 | 253 |
254 | Solution for exercise #3 255 | 256 | ```hs 257 | p_ :: String -> String 258 | p_ = el "p" 259 | 260 | h1_ :: String -> String 261 | h1_ = el "h1" 262 | ``` 263 | 264 |
265 | 266 |
267 | Solution for exercise #4 268 | 269 | ```hs 270 | myhtml :: String 271 | myhtml = 272 | makeHtml 273 | "Hello title" 274 | (h1_ "Hello, world!" <> p_ "Let's learn about Haskell!") 275 | ``` 276 | 277 |
278 | 279 | 280 | 281 | --- 282 | 283 |
284 | Our final program 285 | 286 | ```hs 287 | -- hello.hs 288 | 289 | main :: IO () 290 | main = putStrLn myhtml 291 | 292 | myhtml :: String 293 | myhtml = 294 | makeHtml 295 | "Hello title" 296 | (h1_ "Hello, world!" <> p_ "Let's learn about Haskell!") 297 | 298 | 299 | makeHtml :: String -> String -> String 300 | makeHtml title content = html_ (head_ (title_ title) <> body_ content) 301 | 302 | html_ :: String -> String 303 | html_ = el "html" 304 | 305 | body_ :: String -> String 306 | body_ = el "body" 307 | 308 | head_ :: String -> String 309 | head_ = el "head" 310 | 311 | title_ :: String -> String 312 | title_ = el "title" 313 | 314 | p_ :: String -> String 315 | p_ = el "p" 316 | 317 | h1_ :: String -> String 318 | h1_ = el "h1" 319 | 320 | el :: String -> String -> String 321 | el tag content = 322 | "<" <> tag <> ">" <> content <> " tag <> ">" 323 | ``` 324 | 325 |
326 | -------------------------------------------------------------------------------- /src/03-html/03-edsls.md: -------------------------------------------------------------------------------- 1 | # Embedded Domain-Specific Languages 2 | 3 | Right off the bat, we run into a common pattern in Haskell: creating 4 | Embedded Domain-Specific Languages (EDSLs for short). 5 | 6 | Domain-specific languages (DSLs) are specialized programming languages that are 7 | tailored to specific domains, in contrast to general-purpose languages, 8 | which try to work well in many domains. 9 | 10 | A few examples of DSLs are: 11 | 12 | - make - for defining build systems 13 | - DOT - for defining graphs 14 | - Sed - for defining text transformations 15 | - CSS - for defining styling 16 | - HTML - for defining web pages 17 | 18 | An *embedded* domain-specific language is a little language that is 19 | embedded inside another programming language, making a program written in 20 | the EDSL a valid program in the language it was written in. 21 | 22 | The little HTML library we've been writing can be considered an EDSL. 23 | It is used specifically for building web pages (by returning HTML strings), 24 | and is valid Haskell code! 25 | 26 | In Haskell, we frequently create and use EDSLs to express domain-specific 27 | logic. We have EDSLs for concurrency, command-line options parsing, JSON and HTML, 28 | creating build systems, writing tests, and many more. 29 | 30 | Specialized languages are useful because they can solve specific problems in 31 | a concise (and often safe) way, and by embedding, we get to use the full power of 32 | the host language for our domain logic, including syntax highlighting and 33 | various tools available for the language. 34 | 35 | The drawback of embedding domain-specific languages is that we have to adhere to 36 | the rules of the programming language we embed in, such as syntactic and semantic rules. 37 | 38 | Some languages alleviate this drawback by providing meta-programming capabilities 39 | in the form of macros or other features to extend the language. 40 | And while Haskell does provide such capabilities as well, it is also expressive and concise 41 | enough that many EDSLs do not need them. 42 | 43 | Instead, many Haskell EDSLs use a pattern called _the combinator pattern_: 44 | They define *primitives* and *combinators* - 45 | primitives are basic building blocks of the language, 46 | and combinators are functions that combine primitives into more complex structures. 47 | 48 | In our HTML EDSL, our primitives are functions such as `html_` and `title_` 49 | that can be used to create a single HTML node, and we pass other 50 | constructed nodes as input to these functions, and combine them into a more complex 51 | structure with the append function `<>`. 52 | 53 | There are still a few tricks we can use to make our HTML EDSL better: 54 | 55 | 1. We can use Haskell's type system to make sure we only construct *valid* 56 | HTML, so for example, we don't create a `` node 57 | without a `<head>` node or have user content that 58 | can include unescaped special characters, 59 | and throw a type error when the user tries to do something invalid. 60 | 61 | 2. Our HTML EDSL can move to its own module so it can be reused in multiple modules. 62 | 63 | In the next few sections, we'll take a look at how to define our own types and 64 | how to work with modules to make it harder to make errors, and a little bit 65 | about linked lists in Haskell. 66 | -------------------------------------------------------------------------------- /src/03-html/04-safer_construction.md: -------------------------------------------------------------------------------- 1 | # Safer HTML construction with types 2 | 3 | In this section, we'll learn how to create our own distinguished types 4 | for HTML, and how they can help us avoid the invalid construction of HTML strings. 5 | 6 | There are a few ways of defining new types in Haskell; in this section, 7 | we are going to meet two ways: `newtype` and `type`. 8 | 9 | ## `newtype` 10 | 11 | A `newtype` declaration is a way to define a new, distinct type for an existing set of values. 12 | This is useful when we want to reuse existing values but give them a different meaning 13 | and ensure we can't mix the two. 14 | For example, we can represent seconds, minutes, grams, and yens using integer values, 15 | but we don't want to mix grams and seconds accidentally. 16 | 17 | In our case, we want to represent structured HTML using textual values, 18 | but distinguish them from everyday strings that are not valid HTML. 19 | 20 | A `newtype` declaration looks like this: 21 | 22 | ``` 23 | newtype <type-name> = <constructor> <existing-type> 24 | ``` 25 | 26 | For example, in our case, we can define a distinct type for `Html` like this: 27 | 28 | ```hs 29 | newtype Html = Html String 30 | ``` 31 | 32 | The first `Html`, to the left of the equals sign, lives in the _types_ 33 | name space, meaning that you will only see that name to the right of a 34 | double-colon sign (`::`). 35 | 36 | The second `Html` lives in the _expressions_ (or terms/values) namespace, 37 | meaning that you will see it where you expect expressions (we'll touch where 38 | exactly that can be in a moment). 39 | 40 | The two names, `<type-name>` and `<constructor>`, do not have to be the 41 | same, but they often are. And note that both have to start with a 42 | capital letter. 43 | 44 | The right-hand side of the newtype declaration describes the shape of a 45 | value of that type. In our case, we expect a value of 46 | type `Html` to have the constructor `Html` and then an expression of 47 | type string, for example: `Html "hello"` or `Html ("hello " <> 48 | "world")`. 49 | 50 | You can think of the constructor as a function that takes the argument 51 | and returns something of our new type: 52 | 53 | ```hs 54 | Html :: String -> Html 55 | ``` 56 | 57 | **Note**: We cannot use an expression of type `Html` the same way we'd 58 | use a `String`. So `"hello " <> Html "world"` would fail at type 59 | checking. 60 | 61 | This is useful when we want *encapsulation*. We can define and use 62 | existing representation and functions for our underlying type, but not 63 | mix them with other unrelated (to our domain) types. Similar as 64 | meters and feet can both be numbers, but we don't want to accidentally 65 | add feet to meters without any conversion. 66 | 67 | --- 68 | 69 | For now, let's create a couple of types for our use case. 70 | We want two separate types to represent: 71 | 72 | 1. A complete Html document 73 | 2. A type for html structures such as headings and paragraphs that can go inside the <body> tag 74 | 75 | We want them to be distinct because we don't want to mix them. 76 | 77 | <details> 78 | <summary>Solution</summary> 79 | 80 | ```hs 81 | newtype Html = Html String 82 | 83 | newtype Structure = Structure String 84 | ``` 85 | 86 | </details> 87 | 88 | --- 89 | 90 | ## Using `newtype`s 91 | 92 | To use the underlying type that the newtype wraps, we first 93 | need to extract it out of the type. We do this using pattern matching. 94 | 95 | Pattern matching can be used in two ways, in case-expressions and in 96 | function definitions. 97 | 98 | 1. case expressions are kind of beefed up switch expressions and look like this: 99 | 100 | ``` 101 | case <expression> of 102 | <pattern> -> <expression> 103 | ... 104 | <pattern> -> <expression> 105 | ``` 106 | 107 | The `<expression>` is the thing we want to unpack, and the `pattern` 108 | is its concrete shape. For example, if we wanted to extract the `String` 109 | out of the type `Structure` we defined in the exercise above, we do: 110 | 111 | ```hs 112 | getStructureString :: Structure -> String 113 | getStructureString struct = 114 | case struct of 115 | Structure str -> str 116 | ``` 117 | 118 | This way, we can extract the `String` out of `Structure` and return 119 | it. 120 | 121 | > In later chapters we'll introduce `data` declarations (which are kind of 122 | > a struct + enum chimera), where we can define multiple constructors to 123 | > a type. Then the multiple patterns of a case expression will make more 124 | > sense. 125 | 126 | 2. Alternatively, when declaring a function, we can also use pattern matching on the 127 | arguments: 128 | 129 | ``` 130 | func <pattern> = <expression> 131 | ``` 132 | 133 | For example: 134 | 135 | ```hs 136 | getStructureString :: Structure -> String 137 | getStructureString (Structure str) = str 138 | ``` 139 | 140 | Using the types we created, we can change the HTML functions we've defined before, 141 | namely `html_`, `body_`, `p_`, etc., to operate on these types instead of `String`s. 142 | 143 | But first, let's meet another operator that will make our code more concise. 144 | 145 | One very cool thing about `newtype` is that wrapping and extracting expressions doesn't actually 146 | have a performance cost! The compiler knows how to remove any wrapping and extraction 147 | of the `newtype` constructor and use the underlying type. 148 | 149 | The new type and the constructor we defined are only there to help us *distinguish* between 150 | the type we created and the underlying type when *we write our code*, they are not 151 | needed *when the code is running*. 152 | 153 | `newtype`s provide us with type safety with no performance penalty! 154 | 155 | ## Chaining functions 156 | 157 | Another interesting and extremely common operator 158 | (which is a regular library function in Haskell) is `.` (pronounced compose). 159 | This operator was made to look like the composition operator 160 | you may know from math (`∘`). 161 | 162 | Let's look at its type and implementation: 163 | 164 | ```hs 165 | (.) :: (b -> c) -> (a -> b) -> a -> c 166 | (.) f g x = f (g x) 167 | ``` 168 | 169 | Compose takes 3 arguments: two functions (named `f` and `g` here) and 170 | a third argument named `x`. It then passes the argument `x` to the second 171 | function `g` and calls the first function `f` with the result of `g x`. 172 | 173 | Note that `g` takes as input something of the type 174 | `a` and returns something of the type `b`, and `f` takes 175 | something of the type `b` and returns something of the type `c`. 176 | 177 | Another important thing to note is that types that start with 178 | a _lowercase letter_ are **type variables**. 179 | Think of them as similar to regular variables. Just like 180 | `content` could be any string, like `"hello"` or `"world"`, a type variable 181 | can be any type: `Bool`, `String`, `String -> String`, etc. 182 | This ability is called *parametric polymorphism* (other languages often call this generics). 183 | 184 | The catch is that type variables must match in a signature, so if for 185 | example, we write a function with the type signature `a -> a`, the 186 | input type and the return type **must** match, but it could be 187 | any type - we cannot know what it is. So the only way to implement a 188 | function with that signature is: 189 | 190 | ```hs 191 | id :: a -> a 192 | id x = x 193 | ``` 194 | 195 | `id`, short for the identity function, returns the exact value it received. 196 | If we tried any other way, for example, returning some made-up value 197 | like `"hello"`, or trying to use `x` as a value of a type we know, like 198 | writing `x + x`, the type checker will complain. 199 | 200 | Also, remember that `->` is right-associative? This signature is equivalent to: 201 | 202 | ```hs 203 | (.) :: (b -> c) -> (a -> b) -> (a -> c) 204 | ``` 205 | 206 | Doesn't it look like a function that takes two functions and returns a 207 | third function that is the composition of the two? 208 | 209 | We can now use this operator to change our HTML functions. Let's start 210 | with one example: `p_`. 211 | 212 | Before, we had: 213 | 214 | ```hs 215 | p_ :: String -> String 216 | p_ = el "p" 217 | ``` 218 | 219 | And now, we can write: 220 | 221 | ```hs 222 | p_ :: String -> Structure 223 | p_ = Structure . el "p" 224 | ``` 225 | 226 | The function `p_` will take an arbitrary `String`, which is the content 227 | of the paragraph we wish to create, wrap it in `<p>` and `</p>` tags, 228 | and then wrap it in the `Structure` constructor to produce the 229 | output type `Structure` (remember: newtype constructors can be used as functions!). 230 | 231 | Let's take a deeper look at the types: 232 | 233 | - `Structure :: String -> Structure` 234 | - `el "p" :: String -> String` 235 | - `(.) :: (b -> c) -> (a -> b) -> (a -> c)` 236 | - `Structure . el "p" :: String -> Structure` 237 | 238 | Let's see why the expression `Structure . el "p"` type checks, 239 | and why its type is `String -> Structure`. 240 | 241 | ### Type checking with pen and paper 242 | 243 | If we want to figure out if and how exactly an expression type-checks, 244 | we can do that rather systematically. Let's look at an example 245 | where we try and type-check this expression: 246 | 247 | ```hs 248 | p_ = Structure . el "p" 249 | ``` 250 | 251 | First, we write down the type of the outer-most function. In 252 | our case, this is the operator `.` which has the type: 253 | 254 | ```hs 255 | (.) :: (b -> c) -> (a -> b) -> (a -> c) 256 | ``` 257 | 258 | After that, we can try to **match** the type of the arguments we 259 | apply to this function with the type of the arguments from the type signature. 260 | 261 | In this case, we try to apply two arguments to `.`: 262 | 263 | 1. `Structure :: String -> Structure` 264 | 2. `el "p" :: String -> String` 265 | 266 | And luckily, `.` expects two arguments with the types: 267 | 268 | 1. `b -> c` 269 | 2. `a -> b` 270 | 271 | > Note: Applying a function with more arguments than it expects is a type error. 272 | 273 | Since the `.` operator takes at least the number of arguments we supply, we continue 274 | to the next phase of type-checking: matching the types of the inputs with the types 275 | of the expected inputs (from the type signature of the operator). 276 | 277 | When we match two types, we check for *equivalence* between them. There are a few 278 | possible scenarios here: 279 | 280 | 1. When the two types are **concrete** (as opposed to type variables) 281 | and **simple**, like `Int` and `Bool`, 282 | we check if they are the same. If they are, they type check, and we continue. 283 | If they aren't, they don't type check, and we throw an error. 284 | 2. When the two types we match are more **complex** (for example, both are functions), 285 | we try to match their inputs and outputs (in the case of functions). If the inputs and outputs 286 | match, then the two types match. 287 | 3. There is a special case when one of the types is a **type variable** - 288 | in this case, we treat the matching process like an equation and write it down somewhere. 289 | The next time we see this type variable, we *replace it with its match in the equation*. 290 | Think about this like *assigning* a type *variable* with a *value*. 291 | 292 | In our case, we want to match (or check the equivalence of) these types: 293 | 294 | 1. `String -> Structure` with `b -> c` 295 | 2. `String -> String` with `a -> b` 296 | 297 | Let's do this one by one, starting with (1) - matching `String -> Structure` and `b -> c`: 298 | 299 | 1. Because the two types are complex, we check that they are both functions, match their 300 | inputs and outputs: `String` with `b`, and `Structure` with `c`. 301 | 2. Because `b` is a *type variable*, we mark down somewhere that `b` should 302 | be equivalent to `String`. 303 | We write `b ~ String` (we use `~` to denote equivalence). 304 | 3. We match `Structure` and `c`, same as before, we write down that `c ~ Structure`. 305 | 306 | No problem so far; let's try matching `String -> String` with `a -> b`: 307 | 308 | 1. The two types are complex; we see that both are functions, so we match 309 | their inputs and outputs. 310 | 2. Matching `String` with `a` - we write down that `a ~ String`. 311 | 3. Matching `String` with `b` - we remember that we have already written 312 | about `b` - looking back, we see that we already noted that `b ~ String`. 313 | We need to replace `b` with the type that we wrote down before and 314 | check it against this type, so we match `String` with `String` 315 | which, fortunately, type-check because they are the same. 316 | 317 | So far, so good. We've type-checked the expression and discovered the following equivalences 318 | about the type variables in it: 319 | 320 | 1. `a ~ String` 321 | 2. `b ~ String` 322 | 3. `c ~ Structure` 323 | 324 | Now, when asking what is the type of the expression: 325 | 326 | ```hs 327 | p_ = Structure . el "p" 328 | ``` 329 | 330 | We say that it is the type of `.` after *replacing* the type variables using the equations, we found 331 | and *removing* the inputs we applied to it, so we started with: 332 | 333 | ```hs 334 | (.) :: (b -> c) -> (a -> b) -> (a -> c) 335 | ``` 336 | 337 | Then we replaced the type variables: 338 | 339 | ```hs 340 | (.) :: (String -> Structure) -> (String -> String) -> (String -> Structure) 341 | ``` 342 | 343 | And removed the two arguments when we applied the function: 344 | 345 | ```hs 346 | Structure . el "p" :: String -> Structure 347 | ``` 348 | 349 | And we got the type of expression! 350 | 351 | Fortunately, Haskell can do this process for us. But when Haskell complains 352 | that our types fail to type-check, and we don't understand exactly why, going through this process 353 | can help us understand where the types do not match, and then we can figure out how to solve it. 354 | 355 | 356 | > **Note**: If we use a *parametrically polymorphic* function more than once, 357 | > or use different functions that have similar type variable names, 358 | > the type variables don't have to match in all instances simply because they share a name. 359 | > Each instance has its own unique set of type variables. 360 | > For example, consider the following snippet: 361 | > 362 | > ```hs 363 | > incrementChar :: Char -> Char 364 | > incrementChar c = chr (ord (id c) + id 1) 365 | > ``` 366 | > where the types for the functions we use are: 367 | > 368 | > ```hs 369 | > id :: a -> a 370 | > ord :: Char -> Int 371 | > chr :: Int -> Char 372 | > ``` 373 | > 374 | > In the snippet above, we use `id` twice (for no good reason other than for demonstration purposes). 375 | > The first `id` takes a `Char` as argument, and its `a` is equivalent to `Char`. 376 | > The second `id` takes an `Int` as argument, and its *distinct* `a` is equivalent to `Int`. 377 | > 378 | > This, unfortunately, only applies to functions defined at the top-level. If we'd define a local function 379 | > to be passed as an argument to `incrementChar` with the same type signature as `id`, 380 | > the types must match in all uses. So this code: 381 | > 382 | > ```hs 383 | > incrementChar :: (a -> a) -> Char -> Char 384 | > incrementChar func c = chr (ord (func c) + func 1) 385 | > ``` 386 | > 387 | > Will not type check. Try it! 388 | 389 | ## Appending Structure 390 | 391 | Before, when we wanted to create richer HTML content and appended 392 | nodes to one another, we used the append (`<>`) operator. 393 | Since we are now not using `String` anymore, we need another way 394 | to do it. 395 | 396 | While it is possible to overload `<>` using a feature in 397 | Haskell called type classes, we will instead create a new function 398 | and call it `append_`, and cover type classes later. 399 | 400 | `append_` should take two `Structure`s, and return a third `Structure`, 401 | appending the inner `String` in the first `Structure` to the second and wrapping the result back in `Structure`. 402 | 403 | --- 404 | 405 | Try implementing `append_`. 406 | 407 | <details> 408 | <summary>Solution</summary> 409 | 410 | ```hs 411 | append_ :: Structure -> Structure -> Structure 412 | append_ (Structure a) (Structure b) = 413 | Structure (a <> b) 414 | ``` 415 | 416 | </details> 417 | 418 | --- 419 | 420 | ## Converting back `Html` to `String` 421 | 422 | After constructing a valid `Html` value, we want to be able to 423 | print it to the output so we can display it in our browser. 424 | For that, we need a function that takes an `Html` and converts it to a `String`, which we can then pass to `putStrLn`. 425 | 426 | --- 427 | 428 | Implement the `render` function. 429 | 430 | <details> 431 | <summary>Solution</summary> 432 | 433 | ```hs 434 | render :: Html -> String 435 | render html = 436 | case html of 437 | Html str -> str 438 | ``` 439 | 440 | </details> 441 | 442 | --- 443 | 444 | ## `type` 445 | 446 | Let's look at one more way to give new names to types. 447 | 448 | A `type` definition looks really similar to a `newtype` definition - the only 449 | difference is that we reference the type name directly without a constructor: 450 | 451 | ``` 452 | type <type-name> = <existing-type> 453 | ``` 454 | 455 | For example, in our case, we can write: 456 | 457 | ```hs 458 | type Title = String 459 | ``` 460 | 461 | `type`, in contrast with `newtype`, is just a type name alias. 462 | When we declare `Title` as a *type alias* of `String`, 463 | we mean that `Title` and `String` are interchangeable, 464 | and we can use one or the other whenever we want: 465 | 466 | ```hs 467 | "hello" :: Title 468 | 469 | "hello" :: String 470 | ``` 471 | 472 | Both are valid in this case. 473 | 474 | We can sometimes use `type`s to give a bit more clarity to our code, 475 | but they are much less useful than `newtype`s which allow us to 476 | *distinguish* two types with the same type representation. 477 | 478 | ## The rest of the owl 479 | 480 | --- 481 | 482 | Try changing the code we wrote in previous chapters to use the new types we created. 483 | 484 | > **Tips** 485 | > 486 | > We can combine `makeHtml` and `html_`, and remove `body_` `head_` and `title_` 487 | > by calling `el` directly in `html_`, which can now have the type 488 | > `Title -> Structure -> Html`. 489 | > This will make our HTML EDSL less flexible but more compact. 490 | > 491 | > Alternatively, we could create `newtype`s for `HtmlHead` and `HtmlBody` and 492 | > pass those to `html_`, and we might do that in later chapters, but I've chosen 493 | > to keep the API a bit simple for now, we can always refactor later! 494 | 495 | <details> 496 | <summary>Solution</summary> 497 | 498 | ```hs 499 | -- hello.hs 500 | 501 | main :: IO () 502 | main = putStrLn (render myhtml) 503 | 504 | myhtml :: Html 505 | myhtml = 506 | html_ 507 | "My title" 508 | ( append_ 509 | (h1_ "Heading") 510 | ( append_ 511 | (p_ "Paragraph #1") 512 | (p_ "Paragraph #2") 513 | ) 514 | ) 515 | 516 | newtype Html 517 | = Html String 518 | 519 | newtype Structure 520 | = Structure String 521 | 522 | type Title 523 | = String 524 | 525 | html_ :: Title -> Structure -> Html 526 | html_ title content = 527 | Html 528 | ( el "html" 529 | ( el "head" (el "title" title) 530 | <> el "body" (getStructureString content) 531 | ) 532 | ) 533 | 534 | p_ :: String -> Structure 535 | p_ = Structure . el "p" 536 | 537 | h1_ :: String -> Structure 538 | h1_ = Structure . el "h1" 539 | 540 | el :: String -> String -> String 541 | el tag content = 542 | "<" <> tag <> ">" <> content <> "</" <> tag <> ">" 543 | 544 | append_ :: Structure -> Structure -> Structure 545 | append_ c1 c2 = 546 | Structure (getStructureString c1 <> getStructureString c2) 547 | 548 | getStructureString :: Structure -> String 549 | getStructureString content = 550 | case content of 551 | Structure str -> str 552 | 553 | render :: Html -> String 554 | render html = 555 | case html of 556 | Html str -> str 557 | ``` 558 | 559 | </details> 560 | 561 | --- 562 | 563 | ## Are we safe yet? 564 | 565 | We have made some progress - now we can't write `"Hello"` 566 | where we'd expect either a paragraph or a heading, but we can still 567 | write `Structure "hello"` and get something that isn't a 568 | paragraph or a heading. So while we made it harder for the user 569 | to make mistakes by accident, we haven't really been able to **enforce 570 | the invariants** we wanted to enforce in our library. 571 | 572 | Next, we'll see how we can make expressions such as `Structure "hello"` illegal 573 | as well using *modules* and *smart constructors*. 574 | -------------------------------------------------------------------------------- /src/03-html/05-modules.md: -------------------------------------------------------------------------------- 1 | # Preventing incorrect use with modules 2 | 3 | In this section, we will move the HTML generation library to its own module. 4 | 5 | ## Modules 6 | 7 | Each Haskell source file is a module. The module name should have the 8 | same name as the source file and start with a capital 9 | letter. Sub-directories should also be part of the name, and we use `.` 10 | to denote a sub-directory. We'll see that in the next section. 11 | 12 | The only exception to the rule are entry points to the program - 13 | modules with the name 'Main' that define `main` in them. Their source 14 | file names could have any name they want. 15 | 16 | A module declaration looks like this: 17 | 18 | ```hs 19 | module <module-name> 20 | ( <export-list> 21 | ) 22 | where 23 | ``` 24 | 25 | The export list can be omitted if you want to export everything 26 | defined in the module, but we don't. We will list exactly the 27 | functions and types we want to export. This will give us control 28 | over how people can use our tiny library. 29 | 30 | We will create a new source file named `Html.hs` and add the following 31 | module declaration code at the top of the file: 32 | 33 | ```hs 34 | module Html 35 | ( Html 36 | , Title 37 | , Structure 38 | , html_ 39 | , p_ 40 | , h1_ 41 | , append_ 42 | , render 43 | ) 44 | where 45 | ``` 46 | 47 | Note that we do not export: 48 | 49 | 1. The constructors for our new types, only the types themselves. 50 | If we wanted to export the constructors as well, we would've written 51 | `Html(Html)` or `Html(..)`. This way the user cannot create their own 52 | `Structure` by writing `Structure "Hello"`. 53 | 54 | 2. Internal functions used by the library, such as `el` and `getStructureString`. 55 | 56 | And we will also move the HTML related functions from our `hello.hs` file 57 | to this new `Html.hs` file: 58 | 59 | ```hs 60 | newtype Html 61 | = Html String 62 | 63 | newtype Structure 64 | = Structure String 65 | 66 | type Title 67 | = String 68 | 69 | html_ :: Title -> Structure -> Html 70 | html_ title content = 71 | Html 72 | ( el "html" 73 | ( el "head" (el "title" title) 74 | <> el "body" (getStructureString content) 75 | ) 76 | ) 77 | 78 | p_ :: String -> Structure 79 | p_ = Structure . el "p" 80 | 81 | h1_ :: String -> Structure 82 | h1_ = Structure . el "h1" 83 | 84 | el :: String -> String -> String 85 | el tag content = 86 | "<" <> tag <> ">" <> content <> "</" <> tag <> ">" 87 | 88 | append_ :: Structure -> Structure -> Structure 89 | append_ c1 c2 = 90 | Structure (getStructureString c1 <> getStructureString c2) 91 | 92 | getStructureString :: Structure -> String 93 | getStructureString content = 94 | case content of 95 | Structure str -> str 96 | 97 | render :: Html -> String 98 | render html = 99 | case html of 100 | Html str -> str 101 | ``` 102 | 103 | Now, anyone importing our module (using the `import` statement 104 | below module declarations but above any other 105 | declaration), will only be able to import what we export. 106 | 107 | Add the following code at the top of the `hello.hs` file: 108 | 109 | ```hs 110 | import Html 111 | ``` 112 | 113 | The `hello.hs` file should now look like this: 114 | 115 | ```hs 116 | -- hello.hs 117 | 118 | import Html 119 | 120 | main :: IO () 121 | main = putStrLn (render myhtml) 122 | 123 | myhtml :: Html 124 | myhtml = 125 | html_ 126 | "My title" 127 | ( append_ 128 | (h1_ "Heading") 129 | ( append_ 130 | (p_ "Paragraph #1") 131 | (p_ "Paragraph #2") 132 | ) 133 | ) 134 | ``` 135 | 136 | And the `Html.hs` file should look like this: 137 | 138 | ```hs 139 | -- Html.hs 140 | 141 | module Html 142 | ( Html 143 | , Title 144 | , Structure 145 | , html_ 146 | , p_ 147 | , h1_ 148 | , append_ 149 | , render 150 | ) 151 | where 152 | 153 | newtype Html 154 | = Html String 155 | 156 | newtype Structure 157 | = Structure String 158 | 159 | type Title 160 | = String 161 | 162 | html_ :: Title -> Structure -> Html 163 | html_ title content = 164 | Html 165 | ( el "html" 166 | ( el "head" (el "title" title) 167 | <> el "body" (getStructureString content) 168 | ) 169 | ) 170 | 171 | p_ :: String -> Structure 172 | p_ = Structure . el "p" 173 | 174 | h1_ :: String -> Structure 175 | h1_ = Structure . el "h1" 176 | 177 | el :: String -> String -> String 178 | el tag content = 179 | "<" <> tag <> ">" <> content <> "</" <> tag <> ">" 180 | 181 | append_ :: Structure -> Structure -> Structure 182 | append_ c1 c2 = 183 | Structure (getStructureString c1 <> getStructureString c2) 184 | 185 | getStructureString :: Structure -> String 186 | getStructureString content = 187 | case content of 188 | Structure str -> str 189 | 190 | render :: Html -> String 191 | render html = 192 | case html of 193 | Html str -> str 194 | ``` 195 | 196 | > As an aside, you might have noticed that I've decided to suffix the functions used to 197 | > construct HTML values with an underscore (`_`). This is mostly an aesthetic decision which, 198 | > in my opinion, makes the EDSL easier to recognize, 199 | > but it is also useful to avoid name clashes with 200 | > functions defined in the Haskell standard library, such as `head`. 201 | > I took this idea from a Haskell HTML library named `lucid`! 202 | -------------------------------------------------------------------------------- /src/03-html/06-escaping_characters.md: -------------------------------------------------------------------------------- 1 | # Escaping characters 2 | 3 | Now that `Html` has its own source file and module, and creating 4 | HTML code can be done only via the functions we exported, 5 | we can also handle user input that may contain characters 6 | that may conflict with our meta language, HTML, 7 | such as `<` and `>`, which are used for creating HTML tags. 8 | 9 | We can convert these characters into different strings that HTML can handle. 10 | 11 | See [Stack overflow question](https://stackoverflow.com/questions/7381974/which-characters-need-to-be-escaped-in-html) 12 | for a list of characters, we need to escape. 13 | 14 | Let's create a new function called `escape`: 15 | 16 | ```hs 17 | escape :: String -> String 18 | escape = 19 | let 20 | escapeChar c = 21 | case c of 22 | '<' -> "<" 23 | '>' -> ">" 24 | '&' -> "&" 25 | '"' -> """ 26 | '\'' -> "'" 27 | _ -> [c] 28 | in 29 | concat . map escapeChar 30 | ``` 31 | 32 | In `escape` we see a few new things: 33 | 34 | 1. Let expressions: we can define local names using this syntax: 35 | 36 | ```hs 37 | let 38 | <name> = <expression> 39 | in 40 | <expression> 41 | ``` 42 | 43 | This will make `<name>` available as a variable `in` the second `<expression>`. 44 | 45 | 2. Pattern matching with multiple patterns: we match on different 46 | characters and convert them to a string. Note that `_` is a "catch 47 | all" pattern that will always succeed. 48 | 49 | 3. Two new functions: `map` and `concat`; we'll talk about these in more in-depth 50 | 51 | 4. The syntax highlighting broke a bit for this snippet for some reason. Don't worry about it. 52 | 53 | ## Linked lists briefly 54 | 55 | Linked lists are very common data structures in Haskell, so common that 56 | they have their own special syntax: 57 | 58 | 1. The list types are denoted with brackets, and inside them is the type of the element. For example: 59 | - `[Int]` - a list of integers 60 | - `[Char]` - a list of characters 61 | - `[String]` - a list of strings 62 | - `[[String]]` - a list of a list of strings 63 | - `[a]` - a list of any single type (all elements must be of the same type) 64 | 2. An empty list is written like this: `[]` 65 | 3. Prepending an element to a list is done with the operator `:` (pronounced cons), which is right-associative (like `->`). 66 | For example: `1 : []`, or `1 : 2 : 3 : []`. 67 | 4. The above lists can also be written like `[1]` and `[1, 2, 3]`. 68 | 69 | Also, Strings are linked lists of characters - String is defined as: 70 | `type String = [Char]`, so we can use them the same way we use lists. 71 | 72 | > Do note, however, that linked lists, despite their convenience, are often 73 | > not the right tool for the job. They are not particularly space efficient 74 | > and are slow for appending, random access, and more. That also makes `String` 75 | > a lot less efficient than what it could be. And I generally recommend using a 76 | > different string type, `Text`, instead, which is available in an external package. 77 | > We will talk about lists, `Text`, and other data structures in the future! 78 | 79 | We can implement our own operations on lists by using pattern matching and recursion. 80 | And we'll touch on this subject later when talking about ADTs. 81 | 82 | For now, we will use the various functions found in the 83 | [Data.List](https://hackage.haskell.org/package/base-4.16.4.0/docs/Data-List.html) module. 84 | Specifically, [map](https://hackage.haskell.org/package/base-4.16.4.0/docs/Data-List.html#v:map) 85 | and [concat](https://hackage.haskell.org/package/base-4.16.4.0/docs/Data-List.html#v:concat). 86 | 87 | ### `map` 88 | 89 | Using `map`, we can apply a function to each element in a list. Its type signature is: 90 | 91 | ```hs 92 | map :: (a -> b) -> [a] -> [b] 93 | ``` 94 | 95 | For example: 96 | 97 | ```hs 98 | map not [False, True, False] == [True, False, True] 99 | ``` 100 | 101 | Or as can be seen in our `escape` function, this can help us escape each character: 102 | 103 | ```hs 104 | map escapeChar ['<','h','1','>'] == ["<","h","1",">"] 105 | ``` 106 | 107 | However, note that the `escapeChar` has the type `Char -> String`, 108 | so the result type of `map escapeChar ['<','h','1','>']` is `[String]`, 109 | and what we really want is a `String` and not `[String]`. 110 | 111 | This is where `concat` enters the picture to help us flatten the list. 112 | 113 | ### `concat` 114 | 115 | `concat` has the type: 116 | 117 | ```hs 118 | concat :: [[a]] -> [a] 119 | ``` 120 | 121 | It flattens a list of list of something into a list of something. 122 | In our case it will flatten `[String]` into `String`, remember that 123 | `String` is a **type alias** for `[Char]`, so we actually have 124 | `[[Char]] -> [Char]`. 125 | 126 | ## GHCi 127 | 128 | One way we can quickly see our code in action is by using the interactive development environment **GHCi**. 129 | Running `ghci` will open an interactive prompt where Haskell expressions can be written and 130 | evaluated. This is called a "Read-Evaluate-Print Loop" (for short - REPL). 131 | 132 | For example: 133 | 134 | ``` 135 | ghci> 1 + 1 136 | 2 137 | ghci> putStrLn "Hello, world!" 138 | Hello, world! 139 | ``` 140 | 141 | We can define new names: 142 | 143 | ``` 144 | ghci> double x = x + x 145 | ghci> double 2 146 | 4 147 | ``` 148 | 149 | We can write multi-line code by surrounding it with `:{` and `:}`: 150 | 151 | ``` 152 | ghci> :{ 153 | | escape :: String -> String 154 | | escape = 155 | | let 156 | | escapeChar c = 157 | | case c of 158 | | '<' -> "<" 159 | | '>' -> ">" 160 | | '&' -> "&" 161 | | '"' -> """ 162 | | '\'' -> "'" 163 | | _ -> [c] 164 | | in 165 | | concat . map escapeChar 166 | | :} 167 | 168 | ghci> escape "<html>" 169 | "<html>" 170 | 171 | ``` 172 | 173 | We can import Haskell source files using the `:load` command (`:l` for short): 174 | 175 | ``` 176 | ghci> :load Html.hs 177 | [1 of 1] Compiling Html ( Html.hs, interpreted ) 178 | Ok, one module loaded. 179 | ghci> render (html_ "<title>" (p_ "<body>")) 180 | "<html><head><title><title>

" 181 | ``` 182 | 183 | As well as import library modules: 184 | 185 | ``` 186 | ghci> import Data.Bits 187 | ghci> shiftL 32 1 188 | 64 189 | ghci> clearBit 33 0 190 | 32 191 | ``` 192 | 193 | We can even ask the type of an expression using the `:type` command 194 | (`:t` for short): 195 | 196 | ``` 197 | λ> :type escape 198 | escape :: String -> String 199 | ``` 200 | 201 | To exit `ghci`, use the `:quit` command (or `:q` for short) 202 | 203 | ``` 204 | ghci> :quit 205 | Leaving GHCi. 206 | ``` 207 | 208 | GHCi is a very useful tool for quick experiments and exploration. 209 | We've seen a couple of examples of that above - passing the string `""` to our 210 | `escape` function returns the string `"<html>"`, which can be rendered by 211 | a browser as `` instead of an HTML tag. 212 | 213 | If you are having a hard time figuring out what a particular function does, consider 214 | testing it in GHCi - pass it different inputs and see if it matches your expectations. 215 | Concrete examples of running code can aid a lot in understanding it! 216 | 217 | > If you'd like to learn more about GHCi, you can find a more thorough introduction in the 218 | > [GHC user guide](https://downloads.haskell.org/ghc/latest/docs/users_guide/ghci.html). 219 | 220 | ## Escaping 221 | 222 | --- 223 | 224 | The user of our library can currently only supply strings in a few places: 225 | 226 | 1. Page title 227 | 2. Paragraphs 228 | 3. Headings 229 | 230 | We can apply our escape function at these places before doing anything else with it. 231 | That way, all HTML constructions are safe. 232 | 233 | Try adding the escaping function in those places. 234 | 235 |

236 | Solution 237 | 238 | ```hs 239 | html_ :: Title -> Structure -> Html 240 | html_ title content = 241 | Html 242 | ( el "html" 243 | ( el "head" (el "title" (escape title)) 244 | <> el "body" (getStructureString content) 245 | ) 246 | ) 247 | 248 | p_ :: String -> Structure 249 | p_ = Structure . el "p" . escape 250 | 251 | h1_ :: String -> Structure 252 | h1_ = Structure . el "h1" . escape 253 | ``` 254 | 255 |
256 | 257 | --- 258 | 259 |
260 | Our revised Html.hs 261 | 262 | ```hs 263 | -- Html.hs 264 | 265 | module Html 266 | ( Html 267 | , Title 268 | , Structure 269 | , html_ 270 | , p_ 271 | , h1_ 272 | , append_ 273 | , render 274 | ) 275 | where 276 | 277 | -- * Types 278 | 279 | newtype Html 280 | = Html String 281 | 282 | newtype Structure 283 | = Structure String 284 | 285 | type Title 286 | = String 287 | 288 | -- * EDSL 289 | 290 | html_ :: Title -> Structure -> Html 291 | html_ title content = 292 | Html 293 | ( el "html" 294 | ( el "head" (el "title" (escape title)) 295 | <> el "body" (getStructureString content) 296 | ) 297 | ) 298 | 299 | p_ :: String -> Structure 300 | p_ = Structure . el "p" . escape 301 | 302 | h1_ :: String -> Structure 303 | h1_ = Structure . el "h1" . escape 304 | 305 | append_ :: Structure -> Structure -> Structure 306 | append_ c1 c2 = 307 | Structure (getStructureString c1 <> getStructureString c2) 308 | 309 | -- * Render 310 | 311 | render :: Html -> String 312 | render html = 313 | case html of 314 | Html str -> str 315 | 316 | -- * Utilities 317 | 318 | el :: String -> String -> String 319 | el tag content = 320 | "<" <> tag <> ">" <> content <> " tag <> ">" 321 | 322 | getStructureString :: Structure -> String 323 | getStructureString content = 324 | case content of 325 | Structure str -> str 326 | 327 | escape :: String -> String 328 | escape = 329 | let 330 | escapeChar c = 331 | case c of 332 | '<' -> "<" 333 | '>' -> ">" 334 | '&' -> "&" 335 | '"' -> """ 336 | '\'' -> "'" 337 | _ -> [c] 338 | in 339 | concat . map escapeChar 340 | ``` 341 | 342 |
343 | 344 | Try constructing an invalid HTML in `hello.hs` to see if this works or not! 345 | 346 | Now we can use our tiny HTML library safely. But what if the user 347 | wants to use our library with a valid use case we didn't think about, for 348 | example, adding unordered lists? We are completely blocking them from 349 | extending our library. We'll talk about this next. 350 | -------------------------------------------------------------------------------- /src/03-html/07-internal_modules.md: -------------------------------------------------------------------------------- 1 | # Exposing internal functionality (Internal modules) 2 | 3 | We have now built a very small but convenient and safe way to write 4 | HTML code in Haskell. This is something that we could (potentially) 5 | publish as a *library* and share with the world by uploading it 6 | to a package repository such as [Hackage](https://hackage.haskell.org/). 7 | Users interested in our library could use a package manager 8 | to include it in their project and build their own HTML pages. 9 | 10 | It is important to note that users are building their projects against 11 | the API that we expose to them, and the package manager doesn't generally 12 | provide access to the source code, so they can't, for example, 13 | modify the `Html` module (that we expose) in their project directly 14 | without jumping through some hoops. 15 | 16 | Because we wanted our `Html` EDSL to be safe, we **hid the internal 17 | implementation from the user**, and the only way to interact with the 18 | library is via the API we provide. 19 | 20 | This provides the safety we wanted to provide, but in this case, it also 21 | *blocks* the user from extending our library *in their own project* with 22 | things we haven't implemented yet, such as lists or code blocks. 23 | 24 | When a user runs into trouble with a library (such as missing features) 25 | the best course of action usually is to open an issue in the repository or 26 | submit a pull request, but sometimes the user needs things to work *now*. 27 | 28 | We admit that we are not perfect and can't think of all use cases for our 29 | library. Sometimes the restrictions we add are too great and may limit 30 | the usage of advanced users who know how things work under the hood and 31 | need certain functionality to use our library. 32 | 33 | ### Internal modules 34 | 35 | For that, we can expose internal modules to provide some flexibility for 36 | advanced users. Internal modules are not a language concept but 37 | rather a (fairly common) design pattern (or idiom) in Haskell. 38 | 39 | Internal modules are simply modules named `.Internal`, 40 | which export all of the functionality and implementation details in that module. 41 | 42 | Instead of writing the implementation in (for example) the `Html` module, 43 | we write it in the `Html.Internal` module, which will export everything. 44 | Then we will import that module in the `Html` module and write an explicit export list 45 | to only export the API we'd like to export (as before). 46 | 47 | `Internal` modules are considered unstable and risky to use by convention. 48 | If you end up using one yourself when using an external Haskell library, 49 | make sure to open a ticket in the library's repository after the storm has passed! 50 | 51 | ### Let's make the changes 52 | 53 | We will create a new directory named `Html` and inside it a new file 54 | named `Internal.hs`. The name of this module should be `Html.Internal`. 55 | 56 | This module will contain all of the code we previously had in the `Html` 57 | module, but **we will change the module declaration in `Html.Internal` 58 | and _omit_ the export list**: 59 | 60 | ```hs 61 | -- Html/Internal.hs 62 | 63 | module Html.Internal where 64 | 65 | ... 66 | ``` 67 | 68 | And now in `Html.hs`, we will remove the code that we moved to `Html/Internal.hs` 69 | and in its stead we'll import the internal module: 70 | 71 | ```hs 72 | -- Html.hs 73 | 74 | module Html 75 | ( Html 76 | , Title 77 | , Structure 78 | , html_ 79 | , p_ 80 | , h1_ 81 | , append_ 82 | , render 83 | ) 84 | where 85 | 86 | import Html.Internal 87 | ``` 88 | 89 | Now, users of our library can still import `Html` and safely use our library, 90 | but if they run into trouble and have a dire need to implement unordered lists 91 | to work with our library, they could always work with `Html.Internal` instead. 92 | 93 |
94 | Our revised Html.hs and Html/Internal.hs 95 | 96 | ```hs 97 | -- Html.hs 98 | 99 | module Html 100 | ( Html 101 | , Title 102 | , Structure 103 | , html_ 104 | , p_ 105 | , h1_ 106 | , append_ 107 | , render 108 | ) 109 | where 110 | 111 | import Html.Internal 112 | ``` 113 | 114 | ```hs 115 | -- Html/Internal.hs 116 | 117 | module Html.Internal where 118 | 119 | -- * Types 120 | 121 | newtype Html 122 | = Html String 123 | 124 | newtype Structure 125 | = Structure String 126 | 127 | type Title 128 | = String 129 | 130 | -- * EDSL 131 | 132 | html_ :: Title -> Structure -> Html 133 | html_ title content = 134 | Html 135 | ( el "html" 136 | ( el "head" (el "title" (escape title)) 137 | <> el "body" (getStructureString content) 138 | ) 139 | ) 140 | 141 | p_ :: String -> Structure 142 | p_ = Structure . el "p" . escape 143 | 144 | h1_ :: String -> Structure 145 | h1_ = Structure . el "h1" . escape 146 | 147 | append_ :: Structure -> Structure -> Structure 148 | append_ c1 c2 = 149 | Structure (getStructureString c1 <> getStructureString c2) 150 | 151 | -- * Render 152 | 153 | render :: Html -> String 154 | render html = 155 | case html of 156 | Html str -> str 157 | 158 | -- * Utilities 159 | 160 | el :: String -> String -> String 161 | el tag content = 162 | "<" <> tag <> ">" <> content <> " tag <> ">" 163 | 164 | getStructureString :: Structure -> String 165 | getStructureString content = 166 | case content of 167 | Structure str -> str 168 | 169 | escape :: String -> String 170 | escape = 171 | let 172 | escapeChar c = 173 | case c of 174 | '<' -> "<" 175 | '>' -> ">" 176 | '&' -> "&" 177 | '"' -> """ 178 | '\'' -> "'" 179 | _ -> [c] 180 | in 181 | concat . map escapeChar 182 | ``` 183 | 184 |
185 | 186 | 187 | ## Summary 188 | 189 | For our particular project, `Internal` modules aren't necessary. 190 | Because our project and the source code for the HTML EDSL are 191 | part of the same project, and we have access to the `Html` 192 | module directly, we can always go and edit it if we want 193 | (and we are going to do that throughout the book). 194 | 195 | However, if we were planning to release our HTML EDSL as a *library* 196 | for other developers to use, it would be nice 197 | to also expose the internal implementation as an `Internal` 198 | module. Just so we can save some trouble for potential users! 199 | 200 | In a later chapter, we will see how to create a package from our source code. 201 | -------------------------------------------------------------------------------- /src/03-html/08-exercises.md: -------------------------------------------------------------------------------- 1 | # Exercises 2 | 3 | We need a few more features for our HTML library to be useful for 4 | our blog software. Add the following features to our `Html.Internal` module 5 | and expose them from `Html`. 6 | 7 | ## 1. Unordered lists 8 | 9 | These lists have the form: 10 | 11 | ```html 12 |
    13 |
  • item 1
  • 14 |
  • item 2
  • 15 |
  • ...
  • 16 |
17 | ``` 18 | 19 | We want in our library a new function: 20 | ```hs 21 | ul_ :: [Structure] -> Structure 22 | ``` 23 | 24 | So that users can write this: 25 | 26 | ```hs 27 | ul_ 28 | [ p_ "item 1" 29 | , p_ "item 2" 30 | , p_ "item 3" 31 | ] 32 | ``` 33 | 34 | and get this: 35 | 36 | ```html 37 |
    38 |
  • item 1

  • 39 |
  • item 2

  • 40 |
  • item 3

  • 41 |
42 | ``` 43 | 44 | ## 2. Ordered lists 45 | 46 | Very similar to unordered lists, but instead of `
    ` we use `
      ` 47 | 48 | ## 3. Code blocks 49 | 50 | Very similar to `

      `, but use the `

      ` tag. Call this function `code_`.
      51 | 
      52 | 
      53 | ## Solutions
      54 | 
      55 | 
      56 | Unordered lists 57 | 58 | ```hs 59 | ul_ :: [Structure] -> Structure 60 | ul_ = 61 | Structure . el "ul" . concat . map (el "li" . getStructureString) 62 | ``` 63 | 64 |
      65 | 66 | 67 |
      68 | Ordered lists 69 | 70 | ```hs 71 | ol_ :: [Structure] -> Structure 72 | ol_ = 73 | Structure . el "ol" . concat . map (el "li" . getStructureString) 74 | ``` 75 | 76 | Note: the two functions above could be unified. 77 | 78 |
      79 | 80 | 81 |
      82 | Code blocks 83 | 84 | ```hs 85 | code_ :: String -> Structure 86 | code_ = Structure . el "pre" . escape 87 | ``` 88 | 89 |
      90 | 91 | -------------------------------------------------------------------------------- /src/03-html/09-summary.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | In this chapter, we built a very minimal HTML EDSL. 4 | We will later use this library to convert our custom markup formatted text to HTML. 5 | 6 | We've also learned about: 7 | 8 | - Defining and using functions 9 | - Types and type signatures 10 | - Embedded domain-specific languages 11 | - Chaining functions using the `.` operator 12 | - Preventing incorrect use with `newtype`s 13 | - Defining modules and the `Internal` module pattern 14 | - Encapsulation using `newtype`s and modules 15 | 16 | Here's our complete program up to this point: 17 | 18 | ```hs 19 | -- hello.hs 20 | 21 | import Html 22 | 23 | main :: IO () 24 | main = putStrLn (render myhtml) 25 | 26 | myhtml :: Html 27 | myhtml = 28 | html_ 29 | "My title" 30 | ( append_ 31 | (h1_ "Heading") 32 | ( append_ 33 | (p_ "Paragraph #1") 34 | (p_ "Paragraph #2") 35 | ) 36 | ) 37 | ``` 38 | 39 | ```hs 40 | -- Html.hs 41 | 42 | module Html 43 | ( Html 44 | , Title 45 | , Structure 46 | , html_ 47 | , h1_ 48 | , p_ 49 | , ul_ 50 | , ol_ 51 | , code_ 52 | , append_ 53 | , render 54 | ) 55 | where 56 | 57 | import Html.Internal 58 | ``` 59 | 60 | ```hs 61 | -- Html/Internal.hs 62 | 63 | module Html.Internal where 64 | 65 | -- * Types 66 | 67 | newtype Html 68 | = Html String 69 | 70 | newtype Structure 71 | = Structure String 72 | 73 | type Title 74 | = String 75 | 76 | -- * EDSL 77 | 78 | html_ :: Title -> Structure -> Html 79 | html_ title content = 80 | Html 81 | ( el "html" 82 | ( el "head" (el "title" (escape title)) 83 | <> el "body" (getStructureString content) 84 | ) 85 | ) 86 | 87 | p_ :: String -> Structure 88 | p_ = Structure . el "p" . escape 89 | 90 | h1_ :: String -> Structure 91 | h1_ = Structure . el "h1" . escape 92 | 93 | ul_ :: [Structure] -> Structure 94 | ul_ = 95 | Structure . el "ul" . concat . map (el "li" . getStructureString) 96 | 97 | ol_ :: [Structure] -> Structure 98 | ol_ = 99 | Structure . el "ol" . concat . map (el "li" . getStructureString) 100 | 101 | code_ :: String -> Structure 102 | code_ = Structure . el "pre" . escape 103 | 104 | append_ :: Structure -> Structure -> Structure 105 | append_ c1 c2 = 106 | Structure (getStructureString c1 <> getStructureString c2) 107 | 108 | -- * Render 109 | 110 | render :: Html -> String 111 | render html = 112 | case html of 113 | Html str -> str 114 | 115 | -- * Utilities 116 | 117 | el :: String -> String -> String 118 | el tag content = 119 | "<" <> tag <> ">" <> content <> " tag <> ">" 120 | 121 | getStructureString :: Structure -> String 122 | getStructureString content = 123 | case content of 124 | Structure str -> str 125 | 126 | escape :: String -> String 127 | escape = 128 | let 129 | escapeChar c = 130 | case c of 131 | '<' -> "<" 132 | '>' -> ">" 133 | '&' -> "&" 134 | '"' -> """ 135 | '\'' -> "'" 136 | _ -> [c] 137 | in 138 | concat . map escapeChar 139 | ``` 140 | 141 | > You can also [browse the code as a tree](https://github.com/soupi/learn-haskell-blog-generator/tree/2a4691de627bcb280e92f3d02a88d5404179dc86). 142 | -------------------------------------------------------------------------------- /src/03-html_printer.md: -------------------------------------------------------------------------------- 1 | # Building an HTML printer library 2 | 3 | In this part, we'll explore a few basic building blocks in Haskell, 4 | including functions, types, and modules, while building a small HTML printer library 5 | with which we will later construct HTML pages from our markup blog posts. 6 | 7 | If you're not familiar with HTML and would like a quick tutorial before diving in, MDN's 8 | [Getting started with HTML](https://developer.mozilla.org/en-US/docs/Learn/HTML/Introduction_to_HTML/Getting_started) 9 | is a good overview of the subject. 10 | -------------------------------------------------------------------------------- /src/04-markup.md: -------------------------------------------------------------------------------- 1 | # Custom markup language 2 | 3 | In this chapter, we will define our own simple markup language 4 | and parse documents written in this language into Haskell data structures. 5 | 6 | Our markup language will contain the following features: 7 | 8 | - Headings: prefix by a number of `*` characters 9 | - Paragraphs: a group of lines without empty lines in between 10 | - Unordered lists: a group of lines, each prefixed with `- ` 11 | - Ordered lists: a group of lines, each prefixed with `# ` 12 | - Code blocks: a group of lines, each prefixed with `> ` 13 | 14 | Here's a sample document: 15 | 16 | ```org 17 | * Compiling programs with ghc 18 | 19 | Running ghc invokes the Glasgow Haskell Compiler (GHC), 20 | and can be used to compile Haskell modules and programs into native 21 | executables and libraries. 22 | 23 | Create a new Haskell source file named hello.hs, and write 24 | the following code in it: 25 | 26 | > main = putStrLn "Hello, Haskell!" 27 | 28 | Now, we can compile the program by invoking ghc with the file name: 29 | 30 | > ➜ ghc hello.hs 31 | > [1 of 1] Compiling Main ( hello.hs, hello.o ) 32 | > Linking hello ... 33 | 34 | GHC created the following files: 35 | 36 | - hello.hi - Haskell interface file 37 | - hello.o - Object file, the output of the compiler before linking 38 | - hello (or hello.exe on Microsoft Windows) - A native runnable executable. 39 | 40 | GHC will produce an executable when the source file satisfies both conditions: 41 | 42 | # Defines the main function in the source file 43 | # Defines the module name to be Main or does not have a module declaration 44 | 45 | Otherwise, it will only produce the .o and .hi files. 46 | ``` 47 | 48 | which we will eventually convert into this (modulo formatting) HTML: 49 | 50 | ```html 51 |

      Compiling programs with ghc

      52 | 53 |

      Running ghc invokes the Glasgow Haskell Compiler (GHC), 54 | and can be used to compile Haskell modules and programs into native 55 | executables and libraries. 56 |

      57 | 58 |

      Create a new Haskell source file named hello.hs, and write 59 | the following code in it: 60 |

      61 | 62 |
      main = putStrLn "Hello, Haskell!"
      63 | 
      64 | 65 |

      Now, we can compile the program by invoking ghc with the file name:

      66 | 67 |
      68 | ➜ ghc hello.hs
      69 | [1 of 1] Compiling Main             ( hello.hs, hello.o )
      70 | Linking hello ...
      71 | 
      72 | 73 |

      GHC created the following files: 74 |

      75 | 76 |
        77 |
      • hello.hi - Haskell interface file
      • 78 |
      • hello.o - Object file, the output of the compiler before linking
      • 79 |
      • hello (or hello.exe on Microsoft Windows) - A native runnable executable.
      • 80 |
      81 | 82 |

      GHC will produce an executable when the source file satisfies both conditions: 83 |

      84 | 85 |
        86 |
      1. Defines the main function in the source file
      2. 87 |
      3. Defines the module name to be Main, or does not have a module declaration
      4. 88 |
      89 | 90 |

      Otherwise, it will only produce the .o and .hi files. 91 |

      92 | ``` 93 | -------------------------------------------------------------------------------- /src/04-markup/01-data_type.md: -------------------------------------------------------------------------------- 1 | # Representing the markup language as a Haskell data type 2 | 3 | One of the clear differentiators between Haskell (also other ML-family of languages) 4 | and most mainstream languages is the ability to represent data precisely and succinctly. 5 | 6 | So how do we represent our markup language using Haskell? 7 | 8 | Previously, in our HTML builder library, we used `newtype`s to differentiate 9 | between HTML documents, structures, and titles, but we didn't really need to 10 | differentiate between different kinds of structures, such as paragraphs and headings, 11 | not without parsing the data, at least. 12 | 13 | In this case, we have a list of structures, and each structure could be 14 | one of a few specific options (a paragraph, a heading, a list, etc.), 15 | and we want to be able to know which structure is which so we can easily 16 | convert it into the equivalent HTML representation. 17 | 18 | For that, we have `data` definitions. Using `data` we can 19 | create custom types by grouping multiple types together and having 20 | alternative structures. Think of them as a combination of both structs and enums. 21 | 22 | `data` declarations look like this: 23 | 24 | ```hs 25 | data 26 | = 27 | | 28 | | ... 29 | ``` 30 | 31 | It looks really similar to `newtype`, but there are two important 32 | differences: 33 | 34 | 1. In the `` part, we can write many types (Like `Int`, `String`, or `Bool`). 35 | For `newtype`s, we can only write one. 36 | 2. We can have alternative structures using `|`, `newtype`s have no 37 | alternatives. 38 | 39 | This is because `newtype` is used to provide a type-safe __alias__, and `data` 40 | is used to build a new **composite** type that can potentially have *alternatives*. 41 | 42 | Let's see a few examples of data types: 43 | 44 | 1. Bool 45 | 46 | ```hs 47 | data Bool 48 | = True 49 | | False 50 | ``` 51 | 52 | We created a new data type named `Bool` with the possible values `True` or `False`. 53 | In this case, we only have *constructor* alternatives, and none of the constructors 54 | carry additional values. This is similar to enums in other languages. 55 | 56 | 2. Person 57 | 58 | ```hs 59 | data Person 60 | = Person String Int -- where the first is the name and the second is 61 | -- the age 62 | ``` 63 | 64 | We created a new data type named `Person`. Values of the type `Person` 65 | look like this: 66 | 67 | ``` 68 | Person 69 | ``` 70 | 71 | For example: 72 | 73 | ```hs 74 | Person "Gil" 32 75 | ``` 76 | 77 | In this case, we create a *composite* of multiple types without alternatives. 78 | This is similar to structs in other languages, but structs give each field 79 | a name, and here we distinguish them by position. 80 | 81 | Alternatively, Haskell has *syntactic sugar* for naming fields called **records**. 82 | The above definition can also be written like this: 83 | 84 | 85 | ```hs 86 | data Person 87 | = Person 88 | { name :: String 89 | , age :: Int 90 | } 91 | ``` 92 | 93 | Values of this type can be written exactly as before, 94 | 95 | ```hs 96 | Person "Gil" 32 97 | ``` 98 | 99 | Or with this syntax: 100 | 101 | ```hs 102 | Person { name = "Gil", age = 32 } 103 | ``` 104 | 105 | Haskell will also generate functions that can be used to extract the fields from the composite type: 106 | 107 | ```hs 108 | name :: Person -> String 109 | age :: Person -> Int 110 | ``` 111 | 112 | Which can be used like this: 113 | 114 | ```hs 115 | ghci> age (Person { name = "Gil", age = 32 }) 116 | 32 117 | ``` 118 | 119 | We even have a special syntax for updating specific fields in a record. Of course, 120 | we do not update records in place - we generate a new value instead. 121 | 122 | ```hs 123 | ghci> gil = Person { name = "Gil", age = 32 } 124 | ghci> age (gil { age = 33 }) 125 | 33 126 | ghci> age gil 127 | 32 128 | ``` 129 | 130 | Unfortunately, having specialized functions for each field also means that if we 131 | defined a different data type with the field `age`, the functions which GHC needs 132 | to generate will clash. 133 | 134 | The easiest way to solve this is to give fields unique names, for example 135 | by adding a prefix: 136 | 137 | ```hs 138 | data Person 139 | = Person 140 | { pName :: String 141 | , pAge :: Int 142 | } 143 | ``` 144 | 145 | Another way is by using extensions to the Haskell language, which we will cover 146 | in later chapters. 147 | 148 | 3. Tuple 149 | 150 | ```hs 151 | data Tuple a b 152 | = Tuple a b 153 | ``` 154 | 155 | This is pretty similar to `Person`, but we can plug any type we want 156 | for this definition. For example: 157 | 158 | ```hs 159 | Tuple "Clicked" True :: Tuple String Bool 160 | 161 | Tuple 'a' 'z' :: Tuple Char Char 162 | ``` 163 | 164 | This type has special syntax in Haskell: 165 | 166 | ```hs 167 | ("Clicked", True) :: (String, Bool) 168 | 169 | ('a', 'z') :: (Char, Char) 170 | ``` 171 | 172 | This `Tuple` definition is polymorphic; we define the structure but are able to 173 | plug different types into the structure to get concrete types. You can think of `Tuple` 174 | as a *template* for a data type waiting to be filled or as a **function** waiting 175 | for types as input in order to return a data type. We can even take a look at the "type" 176 | signature of `Tuple` in `ghci` using the `:kind` command. 177 | 178 | ```hs 179 | ghci> data Tuple a b = Tuple a b 180 | ghci> :kind Tuple 181 | Tuple :: * -> * -> * 182 | ``` 183 | 184 | > #### Quick detour: Kinds 185 | > 186 | > The `:kind` command is called as such because the "type" of a type is called a **kind**. 187 | > Kinds can be one of two things, either a `*`, which means a saturated (or concrete) type, 188 | > such as `Int` or `Person`, or an `->` of two kinds, which is, as you might have guessed, 189 | > a type function, taking kind and returning a kind. 190 | > 191 | > Note that only types that have the kind `*` can have values. So, for example, while `Tuple Int` 192 | > is a valid Haskell concept that has the *kind* `* -> *`, and we can write code that will 193 | > work "generically" for all types that have a certain kind (e.g. `* -> *`), we cannot 194 | > construct a value that has the kind `* -> *`. All values have types and all 195 | > types that have values have the kind `*`. 196 | > 197 | > We will talk more about kinds later; let's focus on types for now! 198 | 199 | 4. Either 200 | 201 | ```hs 202 | data Either a b 203 | = Left a 204 | | Right b 205 | ``` 206 | 207 | Similar to Tuple, but instead of having only one constructor, we have 208 | two. This means that we can choose which side we want. Here are a 209 | couple of values of type `Either String Int`: 210 | 211 | ```hs 212 | Left "Hello" 213 | 214 | Right 17 215 | ``` 216 | 217 | This type is useful for modeling errors. Either we succeeded and got 218 | what we wanted (The `Right` constructor with the value), or we didn't 219 | and got an error instead (The `Left` constructor with a string or a 220 | custom error type). 221 | 222 | In our program, we use `data` types to model the different kinds of content types 223 | in our markup language. We tag each structure using the data constructor 224 | and provide the rest of the information (the paragraph text, the list items, etc.) 225 | in the `` section of the data declaration for each constructor: 226 | 227 | ```hs 228 | type Document 229 | = [Structure] 230 | 231 | data Structure 232 | = Heading Natural String 233 | | Paragraph String 234 | | UnorderedList [String] 235 | | OrderedList [String] 236 | | CodeBlock [String] 237 | ``` 238 | 239 | Note: `Natural` is defined in the `base` package but not exported from `Prelude`. 240 | Find out which module to import `Natural` by using [Hoogle](https://hoogle.haskell.org). 241 | 242 | --- 243 | 244 | ### Exercises 245 | 246 | Represent the following markup documents as values of `Document`: 247 | 248 | 1. ```org 249 | Hello, world! 250 | ``` 251 | 252 | 2. ```org 253 | * Welcome 254 | 255 | To this tutorial about Haskell. 256 | ``` 257 | 258 | 3. ```org 259 | Remember that multiple lines with no separation 260 | are grouped together into a single paragraph 261 | but list items remain separate. 262 | 263 | # Item 1 of a list 264 | # Item 2 of the same list 265 | ``` 266 | 267 | 4. ```org 268 | * Compiling programs with ghc 269 | 270 | Running ghc invokes the Glasgow Haskell Compiler (GHC), 271 | and can be used to compile Haskell modules and programs into native 272 | executables and libraries. 273 | 274 | Create a new Haskell source file named hello.hs, and write 275 | the following code in it: 276 | 277 | > main = putStrLn "Hello, Haskell!" 278 | 279 | Now, we can compile the program by invoking ghc with the file name: 280 | 281 | > ➜ ghc hello.hs 282 | > [1 of 1] Compiling Main ( hello.hs, hello.o ) 283 | > Linking hello ... 284 | 285 | GHC created the following files: 286 | 287 | - hello.hi - Haskell interface file 288 | - hello.o - Object file, the output of the compiler before linking 289 | - hello (or hello.exe on Microsoft Windows) - A native runnable executable. 290 | 291 | GHC will produce an executable when the source file satisfies both conditions: 292 | 293 | # Defines the main function in the source file 294 | # Defines the module name to be Main or does not have a module declaration 295 | 296 | Otherwise, it will only produce the .o and .hi files. 297 | ``` 298 | 299 | Solutions: 300 | 301 |
      302 | Solution 1 303 | 304 | ```hs 305 | example1 :: Document 306 | example1 = 307 | [ Paragraph "Hello, world!" 308 | ] 309 | ``` 310 | 311 |
      312 | 313 |
      314 | Solution 2 315 | 316 | ```hs 317 | example2 :: Document 318 | example2 = 319 | [ Heading 1 "Welcome" 320 | , Paragraph "To this tutorial about Haskell." 321 | ] 322 | ``` 323 | 324 |
      325 | 326 |
      327 | Solution 3 328 | 329 | ```hs 330 | example3 :: Document 331 | example3 = 332 | [ Paragraph "Remember that multiple lines with no separation are grouped together into a single paragraph but list items remain separate." 333 | , OrderedList 334 | [ "Item 1 of a list" 335 | , "Item 2 of the same list" 336 | ] 337 | ] 338 | ``` 339 | 340 |
      341 | 342 |
      343 | Solution 4 344 | 345 | ```hs 346 | example4 :: Document 347 | example4 = 348 | [ Heading 1 "Compiling programs with ghc" 349 | , Paragraph "Running ghc invokes the Glasgow Haskell Compiler (GHC), and can be used to compile Haskell modules and programs into native executables and libraries." 350 | , Paragraph "Create a new Haskell source file named hello.hs, and write the following code in it:" 351 | , CodeBlock 352 | [ "main = putStrLn \"Hello, Haskell!\"" 353 | ] 354 | , Paragraph "Now, we can compile the program by invoking ghc with the file name:" 355 | , CodeBlock 356 | [ "➜ ghc hello.hs" 357 | , "[1 of 1] Compiling Main ( hello.hs, hello.o )" 358 | , "Linking hello ..." 359 | ] 360 | , Paragraph "GHC created the following files:" 361 | , UnorderedList 362 | [ "hello.hi - Haskell interface file" 363 | , "hello.o - Object file, the output of the compiler before linking" 364 | , "hello (or hello.exe on Microsoft Windows) - A native runnable executable." 365 | ] 366 | , Paragraph "GHC will produce an executable when the source file satisfies both conditions:" 367 | , OrderedList 368 | [ "Defines the main function in the source file" 369 | , "Defines the module name to be Main or does not have a module declaration" 370 | ] 371 | , Paragraph "Otherwise, it will only produce the .o and .hi files." 372 | ] 373 | ``` 374 | 375 |
      376 | 377 | Add a new module named `Markup` and add the data type definition to it. 378 | Note that in this case, we *do* want to export the constructors of `Structure`. 379 | 380 |
      381 | Solution 382 | 383 | ```hs 384 | -- Markup.hs 385 | 386 | module Markup 387 | ( Document 388 | , Structure(..) 389 | ) 390 | where 391 | 392 | import Numeric.Natural 393 | 394 | type Document 395 | = [Structure] 396 | 397 | data Structure 398 | = Heading Natural String 399 | | Paragraph String 400 | | UnorderedList [String] 401 | | OrderedList [String] 402 | | CodeBlock [String] 403 | ``` 404 | 405 |
      406 | 407 | --- 408 | 409 | ## Translating directly? 410 | 411 | You might ask, "Why do we even need to represent the markup as a type? 412 | Why don't we convert it into HTML as soon as we parse it 413 | instead?". That's a good question and a valid strategy. The reason we 414 | first represent it as a Haskell type is for flexibility and modularity. 415 | 416 | If the parsing code is coupled with HTML generation, we lose the 417 | ability to pre-process the markup document. For example, we might want 418 | to take only a small part of the document (for a summary) and present 419 | it, or create a table of content from headings. Or maybe we'd like to 420 | add other targets and not just HTML - maybe markdown format or a GUI reader? 421 | 422 | Parsing to an "abstract data type" (ADT) representation (one that does 423 | not contain the details of the language, for example, '#' for 424 | ordered lists) gives us the freedom to do so much more than just 425 | conversion to HTML that it's usually worth it, in my opinion, unless you 426 | really need to optimize the process. 427 | -------------------------------------------------------------------------------- /src/04-markup/03-displaying_results.md: -------------------------------------------------------------------------------- 1 | # Displaying the parsing results (type classes) 2 | 3 | We want to be able to print a textual representation of values 4 | of our `Document` type. There are a few ways to do that: 5 | 6 | 1. Write our own function of type `Document -> String`, which we could then print, or 7 | 2. Have Haskell write one for us 8 | 9 | Haskell provides us with a mechanism that can automatically generate the implementation of a 10 | *type class* function called `show`, that will convert our type to `String`. 11 | 12 | The type of the function `show` looks like this: 13 | 14 | ```hs 15 | show :: Show a => a -> String 16 | ``` 17 | 18 | This is something new we haven't seen before. Between `::` and `=>` 19 | you see what is called a __type class constraint__ on the type `a`. What 20 | we say in this signature is that the function `show` can work on any 21 | type that is a member of the type class `Show`. 22 | 23 | Type classes is a feature in Haskell that allows us to declare a common 24 | interface for different types. In our case, Haskell's standard library 25 | defines the type class `Show` in the following way (this is a simplified 26 | version but good enough for our purposes): 27 | 28 | ```hs 29 | class Show a where 30 | show :: a -> String 31 | ``` 32 | 33 | A type class declaration describes a common interface for Haskell types. 34 | `show` is an overloaded function that will work for any type that is an *instance* 35 | of the type class `Show`. 36 | We can define an instance of a type class manually like this: 37 | 38 | ```hs 39 | instance Show Bool where 40 | show x = 41 | case x of 42 | True -> "True" 43 | False -> "False" 44 | ``` 45 | 46 | Defining an instance means providing an implementation for the interface of a specific type. 47 | When we call the function `show` on a data type, the compiler will search the type's `Show` instance, 48 | and use the implementation provided in the instance declaration. 49 | 50 | ```hs 51 | ghci> show True 52 | "True" 53 | ghci> show 187 54 | "187" 55 | ghci> show "Hello" 56 | "\"Hello\"" 57 | ``` 58 | 59 | As seen above, the `show` function converts a value to its textual representation. 60 | That is why `"Hello"` includes the quotes as well. The `Show` type class is usually 61 | used for debugging purposes. 62 | 63 | ## Deriving instances 64 | 65 | It is also possible to automatically generate implementations of a few selected 66 | type classes. Fortunately, `Show` is one of them. 67 | 68 | If all the types in the definition of our data type already implement 69 | an instance of `Show`, we can *automatically derive* it by adding `deriving Show` at the 70 | end of the data definition. 71 | 72 | ```hs 73 | data Structure 74 | = Heading Natural String 75 | | Paragraph String 76 | | UnorderedList [String] 77 | | OrderedList [String] 78 | | CodeBlock [String] 79 | deriving Show 80 | ``` 81 | 82 | Now we can use the function `show :: Show a => a -> String` for any 83 | type that implements an instance of the `Show` type class. For example, with `print`: 84 | 85 | ```hs 86 | print :: Show a => a -> IO () 87 | print = putStrLn . show 88 | ``` 89 | 90 | We can first convert our type to `String` and then write it to the 91 | standard output. 92 | 93 | And because lists also implement `Show` for any element type that has 94 | a `Show` instance, we can now print `Document`s, because they are just 95 | aliases for `[Structure]`. Try it! 96 | 97 | There are many type classes Haskellers use everyday. A couple more are 98 | `Eq` for equality and `Ord` for ordering. These are also special type classes 99 | that can be derived automatically. 100 | 101 | ## Laws 102 | 103 | Type classes often come with "rules" or "laws" that instances should satisfy, 104 | the purpose of these laws is to provide *predictable behaviour* across 105 | instances, so that when we run into a new instance we can be confident 106 | that it will behave in an expected way, and we can write code 107 | that works generically for all instances of a type class while expecting 108 | them to adhere to these rules. 109 | 110 | As an example, let's look at the `Semigroup` type class: 111 | 112 | ```hs 113 | class Semigroup a where 114 | (<>) :: a -> a -> a 115 | ``` 116 | 117 | This type class provides a common interface for types with an operation `<>` 118 | that can combine two values into one in some way. 119 | 120 | This type class also mentions that this `<>` operation should be associative, 121 | meaning that these two sides should evaluate to the same result: 122 | 123 | ``` 124 | x <> (y <> z) = (x <> y) <> z 125 | ``` 126 | 127 | An example of a lawful instance of `Semigroup` is lists with the append operation (`++`): 128 | 129 | ```hs 130 | instance Semigroup [a] where 131 | (<>) = (++) 132 | ``` 133 | 134 | Unfortunately, the Haskell type system cannot "prove" that instances 135 | satisfy these laws, but as a community, we often shun unlawful instances. 136 | 137 | Many data types (together with their respective operations) can 138 | form a `Semigroup`, and instances 139 | don't even have to look similar or have a common analogy/metaphor 140 | (and this is true for many other type classes as well). 141 | 142 | **Type classes are often just _interfaces_ with _laws_** (or expected behaviours if you will). 143 | Approaching them with this mindset can be very liberating! 144 | 145 | To put it differently, **type classes can be used to create abstractions** - 146 | interfaces with laws/expected behaviours where we don't actually care about the 147 | concrete details of the underlying type, just that it *implements a certain 148 | API and behaves in a certain way*. 149 | 150 | Regarding `Semigroup`, we have [previously](../03-html/04-safer_construction.html#appending-htmlstructure) 151 | created a function that looks like `<>` for our `Html` EDSL! 152 | We can add a `Semigroup` instance for our `Structure` data type 153 | and have a nicer API! 154 | 155 | --- 156 | 157 | Exercise: Please do this and remove the `append_` function from the API. 158 | 159 |
      160 | Solution 161 | 162 | Replace this: 163 | 164 | ```hs 165 | append_ :: Structure -> Structure -> Structure 166 | append_ c1 c2 = 167 | Structure (getStructureString c1 <> getStructureString c2) 168 | ``` 169 | 170 | With this: 171 | 172 | ```hs 173 | instance Semigroup Structure where 174 | (<>) c1 c2 = 175 | Structure (getStructureString c1 <> getStructureString c2) 176 | ``` 177 | 178 | And remove the export of `append_` in `Html.hs`. You won't need to further export anything 179 | as type class instances are exported automatically. 180 | 181 | You will also need to replace the usage of `append_` with `<>` in `hello.hs`. 182 | 183 |
      184 | 185 | --- 186 | -------------------------------------------------------------------------------- /src/05-glue.md: -------------------------------------------------------------------------------- 1 | # Gluing things together 2 | 3 | In this chapter, we are going to glue the pieces we built together 4 | and build an actual blog generator. We will: 5 | 6 | 1. Read markup text from a file 7 | 2. Parse the text to a `Document` 8 | 3. Convert the result to our `Html` EDSL 9 | 4. Generate HTML code 10 | 5. Write it to a file 11 | 12 | While doing so, we will learn: 13 | 14 | - How to work with IO 15 | - How to import external libraries to process whole directories and create a simple command-line interface 16 | -------------------------------------------------------------------------------- /src/05-glue/01-markup_to_html.md: -------------------------------------------------------------------------------- 1 | # Converting Markup to HTML 2 | 3 | One key part is missing before we can glue everything together, and that is 4 | to convert our `Markup` data types to `Html`. 5 | 6 | We'll start by creating a new module and importing both the `Markup` and the `Html` modules. 7 | 8 | ```hs 9 | module Convert where 10 | 11 | import qualified Markup 12 | import qualified Html 13 | ``` 14 | 15 | ## Qualified Imports 16 | 17 | This time, we've imported the modules qualified. Qualified imports mean that 18 | instead of exposing the names that we've defined in the imported module to 19 | the general module namespace, they now have to be prefixed with the module name. 20 | 21 | For example, `parse` becomes `Markup.parse`. 22 | If we would've imported `Html.Internal` qualified, we'd have to write 23 | `Html.Internal.el`, which is a bit long. 24 | 25 | We can also give the module a new name with the `as` keyword: 26 | 27 | ```hs 28 | import qualified Html.Internal as HI 29 | ``` 30 | 31 | And write `HI.el` instead. 32 | 33 | I like using qualified imports because readers do not have to guess where a 34 | name comes from. Some modules are even designed to be imported qualified. 35 | For example, the APIs of many container types, such as maps, sets, and vectors, are very similar. 36 | If we want to use multiple containers in a single module, we pretty much have 37 | to use qualified imports so that when we write a function such as `singleton`, 38 | which creates a container with a single value, GHC will know which `singleton` 39 | function we are referring to. 40 | 41 | Some people prefer to use import lists instead of qualified imports, 42 | because qualified names can be a bit verbose and noisy. 43 | I will often prefer qualified imports to import lists, but feel free to 44 | try both solutions and see which fits you better. 45 | For more information about imports, 46 | see this [wiki article](https://wiki.haskell.org/Import). 47 | 48 | ## Converting `Markup.Structure` to `Html.Structure` 49 | 50 | Converting a markup structure to an HTML structure is mostly straightforward 51 | at this point, we need to pattern match on the markup structure and use 52 | the relevant HTML API. 53 | 54 | ```hs 55 | convertStructure :: Markup.Structure -> Html.Structure 56 | convertStructure structure = 57 | case structure of 58 | Markup.Heading 1 txt -> 59 | Html.h1_ txt 60 | 61 | Markup.Paragraph p -> 62 | Html.p_ p 63 | 64 | Markup.UnorderedList list -> 65 | Html.ul_ $ map Html.p_ list 66 | 67 | Markup.OrderedList list -> 68 | Html.ol_ $ map Html.p_ list 69 | 70 | Markup.CodeBlock list -> 71 | Html.code_ (unlines list) 72 | ``` 73 | 74 | Notice that running this code with `-Wall` will reveal that the pattern matching 75 | is *non-exhaustive*. This is because we don't currently have a way to build 76 | headings that are not `h1`. There are a few ways to handle this: 77 | 78 | - Ignore the warning - this will likely fail at runtime one day, and the user will be sad 79 | - Pattern match other cases and add a nice error with the `error` function - it has 80 | the same disadvantage above, but will also no longer notify of the unhandled 81 | cases at compile time 82 | - Pattern match and do the wrong thing - user is still sad 83 | - Encode errors in the type system using `Either`, we'll see how to do this in later 84 | chapters 85 | - Restrict the input - change `Markup.Heading` to not include a number but rather 86 | specific supported headings. This is a reasonable approach 87 | - Implement an HTML function supporting arbitrary headings. Should be straightforward 88 | to do 89 | 90 | > #### What are these `$`? 91 | > 92 | > The dollar sign (`$`) is an operator that we can use to group expressions, like we do with parenthesis. 93 | > we can replace the `$` with invisible parenthesis around the expressions to the left of it, 94 | > and around the expression to the right of it. So that: 95 | > 96 | > ```hs 97 | > Html.ul_ $ map Html.p_ list 98 | > ``` 99 | > is understood as: 100 | > ```hs 101 | > (Html.ul_) (map Html.p_ list) 102 | > ``` 103 | > 104 | > It is a function application operator, it applies the argument on the right of the dollar 105 | > to the function on the left of the dollar. 106 | > 107 | > `$` is right-associative and has very low precedence, which means that: 108 | > it groups to the right, and other operators bind more tightly. 109 | > For example the following expression: 110 | > 111 | > ```hs 112 | > filter (2<) $ map abs $ [-1, -2, -3] <> [4, 5, 6] 113 | > ``` 114 | > is understood as: 115 | > ```hs 116 | > (filter (2<)) ((map abs) ([1, -2, 3] <> [-4, 5, 6])) 117 | > ``` 118 | > 119 | > Which is also equivalent to the following code with less parenthesis: 120 | > ```hs 121 | > filter (2<) (map abs ([1, -2, 3] <> [-4, 5, 6])) 122 | > ``` 123 | > See how information flows from right to left and that `<>` binds more tightly? 124 | > 125 | > This operator is fairly common in Haskell code and it helps us reduce some clutter, 126 | > but feel free to avoid it in favor of parenthesis if you'd like, it's not 127 | > like we're even saving keystrokes with `$`! 128 | 129 | --- 130 | 131 | Exercise: Implement `h_ :: Natural -> String -> Structure` 132 | which we'll use to define arbitrary headings (such as `

      `, `

      `, and so on). 133 | 134 |
      Solution 135 | 136 | ```hs 137 | import Numeric.Natural 138 | 139 | h_ :: Natural -> String -> Structure 140 | h_ n = Structure . el ("h" <> show n) . escape 141 | ``` 142 | 143 | Don't forget to export it from `Html.hs`! 144 | 145 | 146 |
      147 | 148 | 149 | Exercise: Fix `convertStructure` using `h_`. 150 | 151 | 152 |
      Solution 153 | 154 | ```hs 155 | convertStructure :: Markup.Structure -> Html.Structure 156 | convertStructure structure = 157 | case structure of 158 | Markup.Heading n txt -> 159 | Html.h_ n txt 160 | 161 | Markup.Paragraph p -> 162 | Html.p_ p 163 | 164 | Markup.UnorderedList list -> 165 | Html.ul_ $ map Html.p_ list 166 | 167 | Markup.OrderedList list -> 168 | Html.ol_ $ map Html.p_ list 169 | 170 | Markup.CodeBlock list -> 171 | Html.code_ (unlines list) 172 | ``` 173 | 174 |
      175 | 176 | --- 177 | 178 | ## Document -> Html 179 | 180 | To create an `Html` document, we need to use the `html_` function. 181 | This function expects two things: a `Title` and a `Structure`. 182 | 183 | For a title, we could just supply it from outside using the file name. 184 | 185 | To convert our markup `Document` (which is a list of markup `Structure`) 186 | to an HTML `Structure`, we need to convert each markup `Structure` and then 187 | concatenate them together. 188 | 189 | We already know how to convert each markup `Structure`; we can use the 190 | `convertStructure` function we wrote and `map`. This will provide 191 | us with the following function: 192 | 193 | ``` 194 | map convertStructure :: Markup.Document -> [Html.Structure] 195 | ``` 196 | 197 | To concatenate all of the `Html.Structure`, we could try to write a recursive 198 | function. However, we will quickly run into an issue 199 | with the base case: what to do when the list is empty? 200 | 201 | We could just provide a dummy `Html.Structure` that represents an empty 202 | HTML structure. 203 | 204 | Let's add this to `Html.Internal`: 205 | 206 | ```hs 207 | empty_ :: Structure 208 | empty_ = Structure "" 209 | ``` 210 | 211 | --- 212 | 213 | Now we can write our recursive function. Try it! 214 | 215 |
      Solution 216 | 217 | ```hs 218 | concatStructure :: [Structure] -> Structure 219 | concatStructure list = 220 | case list of 221 | [] -> empty_ 222 | x : xs -> x <> concatStructure xs 223 | ``` 224 | 225 |
      226 | 227 | --- 228 | 229 | Remember the `<>` function we implemented as an instance of the `Semigroup` 230 | type class? We mentioned that `Semigroup` is an **abstraction** for things 231 | that implements `(<>) :: a -> a -> a`, where `<>` is associative 232 | (`a <> (b <> c) = (a <> b) <> c`). 233 | 234 | It turns out that having an instance of `Semigroup` and also having a value that represents 235 | an "empty" value is a fairly common pattern. For example, a string can be concatenated, 236 | and the empty string can serve as an "empty" value. 237 | And this is actually a well known **abstraction** called **monoid**. 238 | 239 | ## Monoids 240 | 241 | Actually, "empty" isn't a very good description of what we want, 242 | and isn't very useful as an abstraction. Instead, we can describe it as 243 | an "identity" element that satisfies the following laws: 244 | 245 | - `x <> = x` 246 | - ` <> x = x` 247 | 248 | In other words, if we try to use this "empty" - this identity value, 249 | as one argument to `<>`, we will always get the other argument back. 250 | 251 | For `String`, the empty string, `""`, satisfies this: 252 | 253 | ```hs 254 | "" <> "world" = "world" 255 | "hello" <> "" = "hello" 256 | ``` 257 | 258 | This is, of course, true for any value we'd write and not just "world" and "hello". 259 | 260 | Actually, if we move out of the Haskell world for a second, even integers 261 | with `+` as the associative binary operations `+` (in place of `<>`) 262 | and `0` in place of the identity member form a monoid: 263 | 264 | ```hs 265 | 17 + 0 = 17 266 | 0 + 99 = 99 267 | ``` 268 | 269 | So integers together with the `+` operation form a semigroup, and 270 | together with `0` form a monoid. 271 | 272 | We learn new things from this: 273 | 274 | 1. A monoid is a more specific abstraction over semigroup; it builds on it 275 | by adding a new condition (the existence of an identity member) 276 | 2. This abstraction can be useful! We can write a general `concatStructure` 277 | that could work for any monoid 278 | 279 | And indeed, there exists a type class in `base` called `Monoid`, which has 280 | `Semigroup` as a **super class**. 281 | 282 | ```hs 283 | class Semigroup a => Monoid a where 284 | mempty :: a 285 | ``` 286 | 287 | > Note: this is a simplified version. The 288 | > [actual](https://hackage.haskell.org/package/base-4.16.4.0/docs/Prelude.html#t:Monoid) 289 | > is a bit more complicated because of backward compatibility and performance reasons. 290 | > `Semigroup` was actually introduced in Haskell after `Monoid`! 291 | 292 | We could add an instance of `Monoid` for our HTML `Structure` data type: 293 | 294 | 295 | ```hs 296 | instance Monoid Structure where 297 | mempty = empty_ 298 | ``` 299 | 300 | And now, instead of using our own `concatStructure`, we can use the library function: 301 | 302 | ```hs 303 | mconcat :: Monoid a => [a] -> a 304 | ``` 305 | 306 | Which could theoretically be implemented as: 307 | 308 | ```hs 309 | mconcat :: Monoid a => [a] -> a 310 | mconcat list = 311 | case list of 312 | [] -> mempty 313 | x : xs -> x <> mconcat xs 314 | ``` 315 | 316 | Notice that because `Semigroup` is a *super class* of `Monoid`, 317 | we can still use the `<>` function from the `Semigroup` class 318 | without adding the `Semigroup a` constraint to the left side of `=>`. 319 | By adding the `Monoid a` constraint, we implicitly add a `Semigroup a` 320 | constraint as well! 321 | 322 | This `mconcat` function is very similar to the `concatStructure` function, 323 | but this one works for any `Monoid`, including `Structure`! 324 | Abstractions help us identify common patterns and **reuse** code! 325 | 326 | > Side note: integers with `+` and `0` aren't actually an instance of `Monoid` in Haskell. 327 | > This is because integers can also form a monoid with `*` and `1`! But **there can only 328 | > be one instance per type**. Instead, two other `newtype`s exist that provide that 329 | > functionality, [Sum](https://hackage.haskell.org/package/base-4.16.4.0/docs/Data-Monoid.html#t:Sum) 330 | > and [Product](https://hackage.haskell.org/package/base-4.16.4.0/docs/Data-Monoid.html#t:Product). 331 | > See how they can be used in `ghci`: 332 | > 333 | > ```hs 334 | > ghci> import Data.Monoid 335 | > ghci> Product 2 <> Product 3 -- note, Product is a data constructor 336 | > Product {getProduct = 6} 337 | > ghci> getProduct (Product 2 <> Product 3) 338 | > 6 339 | > ghci> getProduct $ mconcat $ map Product [1..5] 340 | > 120 341 | > ``` 342 | 343 | ## Another abstraction? 344 | 345 | We've used `map` and then `mconcat` twice now. Surely there has to be a function 346 | that unifies this pattern. And indeed, it is called 347 | [`foldMap`](https://hackage.haskell.org/package/base-4.16.4.0/docs/Data-Foldable.html#v:foldMap), 348 | and it works not only for lists but also for any data structure that can be "folded", 349 | or "reduced", into a summary value. This abstraction and type class is called **Foldable**. 350 | 351 | For a simpler understanding of `Foldable`, we can look at `fold`: 352 | 353 | ```hs 354 | fold :: (Foldable t, Monoid m) => t m -> m 355 | 356 | -- compare with 357 | mconcat :: Monoid m => [m] -> m 358 | ``` 359 | 360 | `mconcat` is just a specialized version of `fold` for lists. 361 | And `fold` can be used for any pair of a data structure that implements 362 | `Foldable` and a payload type that implements `Monoid`. This 363 | could be `[]` with `Structure`, or `Maybe` with `Product Int`, or 364 | your new shiny binary tree with `String` as the payload type. But note that 365 | the `Foldable` type must be of *kind* `* -> *`. So, for example `Html` 366 | cannot be a `Foldable`. 367 | 368 | `foldMap` is a function that allows us to apply a function to the 369 | payload type of the `Foldable` type right before combining them 370 | with the `<>` function. 371 | 372 | ```hs 373 | foldMap :: (Foldable t, Monoid m) => (a -> m) -> t a -> m 374 | 375 | -- compare to a specialized version with: 376 | -- - t ~ [] 377 | -- - m ~ Html.Structure 378 | -- - a ~ Markup.Structure 379 | foldMap 380 | :: (Markup.Structure -> Html.Structure) 381 | -> [Markup.Structure] 382 | -> Html.Structure 383 | ``` 384 | 385 | True to its name, it really "maps" before it "folds". You might pause here 386 | and think, "this 'map' we are talking about isn't specific for lists; maybe 387 | that's another abstraction?" Yes. It is actually a very important and 388 | fundamental abstraction called `Functor`. 389 | But I think we had enough abstractions for this chapter. 390 | We'll cover it in a later chapter! 391 | 392 | ## Finishing our conversion module 393 | 394 | Let's finish our code by writing `convert`: 395 | 396 | ```hs 397 | convert :: Html.Title -> Markup.Document -> Html.Html 398 | convert title = Html.html_ title . foldMap convertStructure 399 | ``` 400 | 401 | Now we have a full implementation and can convert markup documents 402 | to HTML: 403 | 404 | ```hs 405 | -- Convert.hs 406 | module Convert where 407 | 408 | import qualified Markup 409 | import qualified Html 410 | 411 | convert :: Html.Title -> Markup.Document -> Html.Html 412 | convert title = Html.html_ title . foldMap convertStructure 413 | 414 | convertStructure :: Markup.Structure -> Html.Structure 415 | convertStructure structure = 416 | case structure of 417 | Markup.Heading n txt -> 418 | Html.h_ n txt 419 | 420 | Markup.Paragraph p -> 421 | Html.p_ p 422 | 423 | Markup.UnorderedList list -> 424 | Html.ul_ $ map Html.p_ list 425 | 426 | Markup.OrderedList list -> 427 | Html.ol_ $ map Html.p_ list 428 | 429 | Markup.CodeBlock list -> 430 | Html.code_ (unlines list) 431 | ``` 432 | 433 | ## Summary 434 | 435 | We learned about: 436 | 437 | - Qualified imports 438 | - Ways to handle errors 439 | - The `Monoid` type class and abstraction 440 | - The `Foldable` type class and abstraction 441 | 442 | Next, we are going to glue our functionality together and learn about 443 | I/O in Haskell! 444 | 445 | > You can view the git commit of 446 | > [the changes we've made](https://github.com/soupi/learn-haskell-blog-generator/commit/ad34f2264e9114f2d7436ff472c78da47055fcfe) 447 | > and the [code up until now](https://github.com/soupi/learn-haskell-blog-generator/tree/ad34f2264e9114f2d7436ff472c78da47055fcfe). 448 | -------------------------------------------------------------------------------- /src/06-errors_and_files.md: -------------------------------------------------------------------------------- 1 | # Handling errors and multiple files 2 | 3 | We left an unimplemented function last chapter, 4 | and there are a few more things left for us to do to actually call our program a static blog generator. 5 | We still need to process multiple files in a directory and create an index landing page with links to other pages. 6 | 7 | ## Links in HTML 8 | 9 | Our HTML EDSL currently does not support links or other content modifiers such as bold and italics. 10 | We should add these so we can use them when creating an index. 11 | 12 | Up until now, we've passed `String` to `Structure`, creating functions such as `p_` 13 | and `h_`. Instead, we can create and pass them a new type, `Content`, which 14 | can be regular text, links, images, and so on. 15 | 16 | --- 17 | 18 | **Exercise**: implement what we've just discussed. Follow the compiler errors and refactor what needs refactoring. 19 | 20 |
      Solution 21 | 22 |
      src/Html/Internal.hs 23 | 24 | ```hs 25 | module HsBlog.Html.Internal where 26 | 27 | import Numeric.Natural 28 | 29 | -- * Types 30 | 31 | newtype Html 32 | = Html String 33 | 34 | newtype Structure 35 | = Structure String 36 | 37 | newtype Content 38 | = Content String 39 | 40 | type Title 41 | = String 42 | 43 | -- * EDSL 44 | 45 | html_ :: Title -> Structure -> Html 46 | html_ title content = 47 | Html 48 | ( el "html" 49 | ( el "head" (el "title" (escape title)) 50 | <> el "body" (getStructureString content) 51 | ) 52 | ) 53 | 54 | -- * Structure 55 | 56 | p_ :: Content -> Structure 57 | p_ = Structure . el "p" . getContentString 58 | 59 | h_ :: Natural -> Content -> Structure 60 | h_ n = Structure . el ("h" <> show n) . getContentString 61 | 62 | ul_ :: [Structure] -> Structure 63 | ul_ = 64 | Structure . el "ul" . concat . map (el "li" . getStructureString) 65 | 66 | ol_ :: [Structure] -> Structure 67 | ol_ = 68 | Structure . el "ol" . concat . map (el "li" . getStructureString) 69 | 70 | code_ :: String -> Structure 71 | code_ = Structure . el "pre" . escape 72 | 73 | instance Semigroup Structure where 74 | (<>) c1 c2 = 75 | Structure (getStructureString c1 <> getStructureString c2) 76 | 77 | instance Monoid Structure where 78 | mempty = Structure "" 79 | 80 | -- * Content 81 | 82 | txt_ :: String -> Content 83 | txt_ = Content . escape 84 | 85 | link_ :: FilePath -> Content -> Content 86 | link_ path content = 87 | Content $ 88 | elAttr 89 | "a" 90 | ("href=\"" <> escape path <> "\"") 91 | (getContentString content) 92 | 93 | img_ :: FilePath -> Content 94 | img_ path = 95 | Content $ " escape path <> "\">" 96 | 97 | b_ :: Content -> Content 98 | b_ content = 99 | Content $ el "b" (getContentString content) 100 | 101 | i_ :: Content -> Content 102 | i_ content = 103 | Content $ el "i" (getContentString content) 104 | 105 | instance Semigroup Content where 106 | (<>) c1 c2 = 107 | Content (getContentString c1 <> getContentString c2) 108 | 109 | instance Monoid Content where 110 | mempty = Content "" 111 | 112 | -- * Render 113 | 114 | render :: Html -> String 115 | render html = 116 | case html of 117 | Html str -> str 118 | 119 | -- * Utilities 120 | 121 | el :: String -> String -> String 122 | el tag content = 123 | "<" <> tag <> ">" <> content <> " tag <> ">" 124 | 125 | elAttr :: String -> String -> String -> String 126 | elAttr tag attrs content = 127 | "<" <> tag <> " " <> attrs <> ">" <> content <> " tag <> ">" 128 | 129 | getStructureString :: Structure -> String 130 | getStructureString structure = 131 | case structure of 132 | Structure str -> str 133 | 134 | getContentString :: Content -> String 135 | getContentString content = 136 | case content of 137 | Content str -> str 138 | 139 | escape :: String -> String 140 | escape = 141 | let 142 | escapeChar c = 143 | case c of 144 | '<' -> "<" 145 | '>' -> ">" 146 | '&' -> "&" 147 | '"' -> """ 148 | '\'' -> "'" 149 | _ -> [c] 150 | in 151 | concat . map escapeChar 152 | 153 | 154 | ``` 155 | 156 |
      157 | 158 |
      src/Html.hs 159 | 160 | ```hs 161 | module HsBlog.Html 162 | ( Html 163 | , Title 164 | , Structure 165 | , html_ 166 | , p_ 167 | , h_ 168 | , ul_ 169 | , ol_ 170 | , code_ 171 | , Content 172 | , txt_ 173 | , img_ 174 | , link_ 175 | , b_ 176 | , i_ 177 | , render 178 | ) 179 | where 180 | 181 | import HsBlog.Html.Internal 182 | ``` 183 | 184 |
      185 | 186 |
      src/Convert.hs 187 | 188 | ```hs 189 | module HsBlog.Convert where 190 | 191 | import qualified HsBlog.Markup as Markup 192 | import qualified HsBlog.Html as Html 193 | 194 | convert :: Html.Title -> Markup.Document -> Html.Html 195 | convert title = Html.html_ title . foldMap convertStructure 196 | 197 | convertStructure :: Markup.Structure -> Html.Structure 198 | convertStructure structure = 199 | case structure of 200 | Markup.Heading n txt -> 201 | Html.h_ n $ Html.txt_ txt 202 | 203 | Markup.Paragraph p -> 204 | Html.p_ $ Html.txt_ p 205 | 206 | Markup.UnorderedList list -> 207 | Html.ul_ $ map (Html.p_ . Html.txt_) list 208 | 209 | Markup.OrderedList list -> 210 | Html.ol_ $ map (Html.p_ . Html.txt_) list 211 | 212 | Markup.CodeBlock list -> 213 | Html.code_ (unlines list) 214 | ``` 215 | 216 |
      217 | 218 |
      219 | 220 | --- 221 | 222 | > You can view the git commit of 223 | > [the changes we've made](https://github.com/soupi/learn-haskell-blog-generator/commit/110a19029f0be42eb2ac656f5d38356dbf9c5746) 224 | > and the [code up until now](https://github.com/soupi/learn-haskell-blog-generator/tree/110a19029f0be42eb2ac656f5d38356dbf9c5746). 225 | 226 | ## Creating an index page 227 | 228 | With our extended HTML EDSL, we can now create an index page with links to the other pages. 229 | 230 | To create an index page, we need a list of files with their *target destinations*, 231 | as well as their `Markup` (so we can extract information to include in our index page, 232 | such as the first heading and paragraph). Our output should be an `Html` page. 233 | 234 | --- 235 | 236 | We need to implement the following function: 237 | 238 | ```hs 239 | buildIndex :: [(FilePath, Markup.Document)] -> Html.Html 240 | ``` 241 | 242 |
      Solution 243 | 244 | ```hs 245 | buildIndex :: [(FilePath, Markup.Document)] -> Html.Html 246 | buildIndex files = 247 | let 248 | previews = 249 | map 250 | ( \(file, doc) -> 251 | case doc of 252 | Markup.Heading 1 heading : article -> 253 | Html.h_ 3 (Html.link_ file (Html.txt_ heading)) 254 | <> foldMap convertStructure (take 3 article) 255 | <> Html.p_ (Html.link_ file (Html.txt_ "...")) 256 | _ -> 257 | Html.h_ 3 (Html.link_ file (Html.txt_ file)) 258 | ) 259 | files 260 | in 261 | Html.html_ 262 | "Blog" 263 | ( Html.h_ 1 (Html.link_ "index.html" (Html.txt_ "Blog")) 264 | <> Html.h_ 2 (Html.txt_ "Posts") 265 | <> mconcat previews 266 | ) 267 | ``` 268 | 269 | 270 |
      271 | 272 | --- 273 | 274 | ## Processing directories 275 | 276 | Our general strategy for processing whole directories is going to be: 277 | 278 | - Create the output directory 279 | - Grab all file names in a directory 280 | - Filter them according to their extension; we want to process the `txt` files and 281 | copy other files without modification 282 | - We want to parse each text file, build an index of the result, 283 | convert the files to HTML, and write everything to the target directory 284 | 285 | While our parsing function can't really fail, trying to read or write a file 286 | to the file-system can fail in several ways. It would be nice if our 287 | static blog generator was robust enough that it wouldn't fail completely if one 288 | single file gave it some trouble. This is an excellent opportunity to learn about 289 | error handling in Haskell, both in uneffectful code and for I/O code. 290 | 291 | In the next few chapters, we'll survey the landscape of error handling in Haskell 292 | before figuring out the right approach for our use case. 293 | -------------------------------------------------------------------------------- /src/06-errors_and_files/01-either.md: -------------------------------------------------------------------------------- 1 | # Handling errors with Either 2 | 3 | There are quite a few ways to indicate and handle errors in Haskell. 4 | We are going to look at one solution: using the type 5 | [Either](https://hackage.haskell.org/package/base-4.12.0.0/docs/Data-Either.html). 6 | Either is defined like this: 7 | 8 | ```hs 9 | data Either a b 10 | = Left a 11 | | Right b 12 | ``` 13 | 14 | Simply put, a value of type `Either a b` can contain either a value of type `a`, 15 | or a value of type `b`. 16 | We can tell them apart from the constructor used. 17 | 18 | ```hs 19 | Left True :: Either Bool b 20 | Right 'a' :: Either a Char 21 | ``` 22 | 23 | With this type, we can use the 24 | `Left` constructor to indicate failure with some error value attached, 25 | and the `Right` constructor with one type to represent success with the 26 | expected result. 27 | 28 | Since `Either` is polymorphic, we can use any two types to represent 29 | failure and success. It is often useful to describe the failure modes 30 | using an ADT. 31 | 32 | For example, let's say that we want to parse a `Char` as a decimal digit 33 | to an `Int`. This operation could fail if the Character is not a digit. 34 | We can represent this error as a data type: 35 | 36 | ```hs 37 | data ParseDigitError 38 | = NotADigit Char 39 | deriving Show 40 | ``` 41 | 42 | And our parsing function can have the type: 43 | 44 | ```hs 45 | parseDigit :: Char -> Either ParseDigitError Int 46 | ``` 47 | 48 | Now when we implement our parsing function, we can return `Left` on an error 49 | describing the problem, and `Right` with the parsed value on successful parsing: 50 | 51 | 52 | ```hs 53 | parseDigit :: Char -> Either ParseDigitError Int 54 | parseDigit c = 55 | case c of 56 | '0' -> Right 0 57 | '1' -> Right 1 58 | '2' -> Right 2 59 | '3' -> Right 3 60 | '4' -> Right 4 61 | '5' -> Right 5 62 | '6' -> Right 6 63 | '7' -> Right 7 64 | '8' -> Right 8 65 | '9' -> Right 9 66 | _ -> Left (NotADigit c) 67 | ``` 68 | 69 | `Either a` is also an instance of `Functor` and `Applicative`, 70 | so we have some combinators to work with if we want to combine these 71 | kinds of computations. 72 | 73 | For example, if we had three characters and we wanted to try and parse 74 | each of them and then find the maximum between them; we could use the 75 | applicative interface: 76 | 77 | ```hs 78 | max3chars :: Char -> Char -> Char -> Either ParseDigitError Int 79 | max3chars x y z = 80 | (\a b c -> max a (max b c)) 81 | <$> parseDigit x 82 | <*> parseDigit y 83 | <*> parseDigit z 84 | ``` 85 | 86 | 87 | The `Functor` and `Applicative` interfaces of `Either a` allow us to 88 | apply functions to the payload values and **delay** the error handling to a 89 | later phase. Semantically, the first Either in order that returns a `Left` 90 | will be the return value. We can see how this works in the implementation 91 | of the applicative instance: 92 | 93 | ```hs 94 | instance Applicative (Either e) where 95 | pure = Right 96 | Left e <*> _ = Left e 97 | Right f <*> r = fmap f r 98 | ``` 99 | 100 | At some point, someone will actually want to **inspect** the result 101 | and see if we get an error (with the `Left` constructor) or the expected value 102 | (with the `Right` constructor) and they can do that by pattern-matching the result. 103 | 104 | ## Applicative + Traversable 105 | 106 | The `Applicative` interface of `Either` is very powerful and can be combined 107 | with another abstraction called 108 | [`Traversable`](https://hackage.haskell.org/package/base-4.16.4.0/docs/Data-Traversable.html#g:1) - 109 | for data structures that can be traversed from left to right, like a linked list or a binary tree. 110 | With these, we can combine an unspecified amount of values such as `Either ParseDigitError Int`, 111 | as long as they are all in a data structure that implements `Traversable`. 112 | 113 | Let's see an example: 114 | 115 | ```hs 116 | ghci> :t "1234567" 117 | "1234567" :: String 118 | -- remember, a String is an alias for a list of Char 119 | ghci> :info String 120 | type String :: * 121 | type String = [Char] 122 | -- Defined in ‘GHC.Base’ 123 | 124 | ghci> :t map parseDigit "1234567" 125 | map parseDigit "1234567" :: [Either ParseDigitError Int] 126 | ghci> map parseDigit "1234567" 127 | [Right 1,Right 2,Right 3,Right 4,Right 5,Right 6,Right 7] 128 | 129 | ghci> :t sequenceA 130 | sequenceA :: (Traversable t, Applicative f) => t (f a) -> f (t a) 131 | -- Substitute `t` with `[]`, and `f` with `Either Error` for a specialized version 132 | 133 | ghci> sequenceA (map parseDigit "1234567") 134 | Right [1,2,3,4,5,6,7] 135 | 136 | ghci> map parseDigit "1a2" 137 | [Right 1,Left (NotADigit 'a'),Right 2] 138 | ghci> sequenceA (map parseDigit "1a2") 139 | Left (NotADigit 'a') 140 | ``` 141 | 142 | The pattern of doing `map` and then `sequenceA` is another function called `traverse`: 143 | 144 | ```hs 145 | ghci> :t traverse 146 | traverse 147 | :: (Traversable t, Applicative f) => (a -> f b) -> t a -> f (t b) 148 | ghci> traverse parseDigit "1234567" 149 | Right [1,2,3,4,5,6,7] 150 | ghci> traverse parseDigit "1a2" 151 | Left (NotADigit 'a') 152 | ``` 153 | 154 | We can use `traverse` on any two types where one implements the `Applicative` 155 | interface, like `Either a` or `IO`, and the other implements the `Traversable` interface, 156 | like `[]` (linked lists) and 157 | [`Map k`](https://hackage.haskell.org/package/containers-0.6.5.1/docs/Data-Map-Strict.html#t:Map) 158 | (also known as a dictionary in other languages - a mapping from keys to values). 159 | For example, using `IO` and `Map`. Note that we can construct a `Map` data structure 160 | from a list of tuples using the 161 | [`fromList`](https://hackage.haskell.org/package/containers-0.6.5.1/docs/Data-Map-Strict.html#v:fromList) 162 | function - the first value in the tuple is the key, and the second is the type. 163 | 164 | ```hs 165 | ghci> import qualified Data.Map as M -- from the containers package 166 | 167 | ghci> file1 = ("output/file1.html", "input/file1.txt") 168 | ghci> file2 = ("output/file2.html", "input/file2.txt") 169 | ghci> file3 = ("output/file3.html", "input/file3.txt") 170 | ghci> files = M.fromList [file1, file2, file3] 171 | ghci> :t files :: M.Map FilePath FilePath -- FilePath is an alias of String 172 | files :: M.Map FilePath FilePath :: M.Map FilePath FilePath 173 | 174 | ghci> readFiles = traverse readFile 175 | ghci> :t readFiles 176 | readFiles :: Traversable t => t FilePath -> IO (t String) 177 | 178 | ghci> readFiles files 179 | fromList [("output/file1.html","I'm the content of file1.txt\n"),("output/file2.html","I'm the content of file2.txt\n"),("output/file3.html","I'm the content of file3.txt\n")] 180 | ghci> :t readFiles files 181 | readFiles files :: IO (Map String String) 182 | ``` 183 | 184 | Above, we created a function `readFiles` that will take a mapping from *output file path* 185 | to *input file path* and returns an IO operation that, when run will read the input files 186 | and replace their contents right there in the map! Surely this will be useful later. 187 | 188 | ## Multiple errors 189 | 190 | Note, since `Either` has the kind `* -> * -> *` (it takes two type 191 | parameters) `Either` cannot be an instance of `Functor` or `Applicative`: 192 | instances of these type classes must have the 193 | kind `* -> *`. 194 | Remember that when we look at a type class function signature like: 195 | 196 | ```hs 197 | fmap :: Functor f => (a -> b) -> f a -> f b 198 | ``` 199 | 200 | And if we want to implement it for a specific type (in place of the `f`), 201 | we need to be able to *substitute* the `f` with the target type. If we'd try 202 | to do it with `Either` we would get: 203 | 204 | ```hs 205 | fmap :: (a -> b) -> Either a -> Either b 206 | ``` 207 | 208 | And neither `Either a` or `Either b` are *saturated*, so this won't type check. 209 | For the same reason, if we'll try to substitute `f` with, say, `Int`, we'll get: 210 | 211 | ```hs 212 | fmap :: (a -> b) -> Int a -> Int b 213 | ``` 214 | 215 | Which also doesn't make sense. 216 | 217 | While we can't use `Either`, we can use `Either e`, which has the kind 218 | `* -> *`. Now let's try substituting `f` with `Either e` in this signature: 219 | 220 | ```hs 221 | liftA2 :: Applicative => (a -> b -> c) -> f a -> f b -> f c 222 | ``` 223 | 224 | And we'll get: 225 | 226 | ```hs 227 | liftA2 :: (a -> b -> c) -> Either e a -> Either e b -> Either e c 228 | ``` 229 | 230 | What this teaches us is that we can only use the applicative interface to 231 | combine two *`Either`s with the same type for the `Left` constructor*. 232 | 233 | So what can we do if we have two functions that can return different errors? 234 | There are a few approaches; the most prominent ones are: 235 | 236 | 1. Make them return the same error type. Write an ADT that holds all possible 237 | error descriptions. This can work in some cases but isn't always ideal. 238 | For example, a user calling `parseDigit` shouldn't be forced to 239 | handle a possible case that the input might be an empty string 240 | 2. Use a specialized error type for each type, and when they are composed together, 241 | map the error type of each function to a more general error type. This can 242 | be done with the function 243 | [`first`](https://hackage.haskell.org/package/base-4.16.4.0/docs/Data-Bifunctor.html#v:first) 244 | from the `Bifunctor` type class 245 | 246 | ## Monadic interface 247 | 248 | The applicative interface allows us to lift a function to work on multiple 249 | `Either` values (or other applicative functor instances such as `IO` and `Parser`). 250 | But more often than not, we'd like to use a value from one computation 251 | that might return an error in another computation that might return an error. 252 | 253 | For example, a compiler such as GHC operates in stages, such as lexical analysis, 254 | parsing, type-checking, and so on. Each stage depends on the output of the stage 255 | before it, and each stage might fail. We can write the types for these functions: 256 | 257 | ```hs 258 | tokenize :: String -> Either Error [Token] 259 | 260 | parse :: [Token] -> Either Error AST 261 | 262 | typecheck :: AST -> Either Error TypedAST 263 | ``` 264 | 265 | We want to compose these functions so that they work in a chain. The output of `tokenize` 266 | goes to `parse`, and the output of `parse` goes to `typecheck`. 267 | 268 | We know that we can lift a function over an `Either` (and other functors), 269 | we can also lift a function that returns an `Either`: 270 | 271 | ```hs 272 | -- reminder the type of fmap 273 | fmap :: Functor f => (a -> b) -> f a -> f b 274 | -- specialized for `Either Error` 275 | fmap :: (a -> b) -> Either Error a -> Either Error b 276 | 277 | -- here, `a` is [Token] and `b` is `Either Error AST`: 278 | 279 | > fmap parse (tokenize string) :: Either Error (Either Error AST) 280 | ``` 281 | 282 | While this code compiles, it isn't great, because we are building 283 | layers of `Either Error`, and we can't use this trick again with 284 | `typecheck`! `typecheck` expects an `AST`, but if we try to fmap it 285 | on `fmap parse (tokenize string)`, the `a` will be `Either Error AST` 286 | instead. 287 | 288 | What we would really like is to flatten this structure instead of nesting it. 289 | If we look at the kind of values `Either Error (Either Error AST)` could have, 290 | it looks something like this: 291 | 292 | - `Left ` 293 | - `Right (Left error)` 294 | - `Right (Right )` 295 | 296 | --- 297 | 298 | **Exercise**: What if we just used pattern matching for this instead? What would this look like? 299 | 300 |
      Solution 301 | 302 | ```hs 303 | case tokenize string of 304 | Left err -> 305 | Left err 306 | Right tokens -> 307 | case parse tokens of 308 | Left err -> 309 | Left err 310 | Right ast -> 311 | typecheck ast 312 | ``` 313 | 314 | If we run into an error in a stage, we return that error and stop. If we succeed, we 315 | use the value in the next stage. 316 | 317 |
      318 | 319 | --- 320 | 321 | Flattening this structure for `Either` is very similar to that last part - the body 322 | of the `Right tokens` case: 323 | 324 | ```hs 325 | flatten :: Either e (Either e a) -> Either e a 326 | flatten e = 327 | case e of 328 | Left l -> Left l 329 | Right x -> x 330 | ``` 331 | 332 | Because we have this function, we can now use it on the output of 333 | `fmap parse (tokenize string) :: Either Error (Either Error AST)` 334 | from before: 335 | 336 | ``` 337 | > flatten (fmap parse (tokenize string)) :: Either Error AST 338 | ``` 339 | 340 | And now, we can use this function again to compose with `typecheck`: 341 | 342 | ```hs 343 | > flatten (fmap typecheck (flatten (fmap parse (tokenize string)))) :: Either Error TypedAST 344 | ``` 345 | 346 | This `flatten` + `fmap` combination looks like a recurring pattern which 347 | we can combine into a function: 348 | 349 | ```hs 350 | flatMap :: (a -> Either e b) -> Either e a -> Either e b 351 | flatMap func val = flatten (fmap func val) 352 | ``` 353 | 354 | And now, we can write the code this way: 355 | 356 | ```hs 357 | > flatMap typecheck (flatMap parse (tokenize string)) :: Either Error TypedAST 358 | 359 | -- Or using backticks syntax to convert the function to infix form: 360 | > typecheck `flatMap` parse `flatMap` tokenize string 361 | 362 | -- Or create a custom infix operator: (=<<) = flatMap 363 | > typeCheck =<< parse =<< tokenize string 364 | ``` 365 | 366 | 367 | This function, `flatten` (and `flatMap` as well), have different names in Haskell. 368 | They are called 369 | [`join`](https://hackage.haskell.org/package/base-4.16.4.0/docs/Control-Monad.html#v:join) 370 | and [`=<<`](https://hackage.haskell.org/package/base-4.16.4.0/docs/Control-Monad.html#v:-61--60--60-) 371 | (pronounced "reverse bind"), 372 | and they are the essence of another incredibly useful abstraction in Haskell. 373 | 374 | If we have a type that can implement: 375 | 376 | 1. The `Functor` interface, specifically the `fmap` function 377 | 2. The `Applicative` interface, most importantly the `pure` function 378 | 3. This `join` function 379 | 380 | They can implement an instance of the `Monad` type class. 381 | 382 | With functors, we were able to "lift" a function to work over the type implementing the functor type class: 383 | 384 | ```hs 385 | fmap :: (a -> b) -> f a -> f b 386 | ``` 387 | 388 | With applicative functors we were able to "lift" a function of multiple arguments 389 | over multiple values of a type implementing the applicative functor type class, 390 | and also lift a value into that type: 391 | 392 | ```hs 393 | pure :: a -> f a 394 | 395 | liftA2 :: (a -> b -> c) -> f a -> f b -> f c 396 | ``` 397 | 398 | With monads we can now flatten (or "join" in Haskell terminology) types that implement 399 | the `Monad` interface: 400 | 401 | ```hs 402 | join :: m (m a) -> m a 403 | 404 | -- this is =<< with the arguments reversed, pronounced "bind" 405 | (>>=) :: m a -> (a -> m b) -> m b 406 | ``` 407 | 408 | With `>>=`, we can write our compilation pipeline from before in a left-to-right 409 | manner, which seems to be more popular for monads: 410 | 411 | ```hs 412 | > tokenize string >>= parse >>= typecheck 413 | ``` 414 | 415 | We had already met this function before when we talked about `IO`. Yes, 416 | `IO` also implements the `Monad` interface. The monadic interface for `IO` 417 | helped us with creating a proper ordering of effects. 418 | 419 | The essence of the `Monad` interface is the `join`/`>>=` functions, and as we've seen 420 | we can implement `>>=` in terms of `join`, we can also implement `join` in terms 421 | of `>>=` (try it!). 422 | 423 | The monadic interface can mean very different things for different types. For `IO` this 424 | is ordering of effects, for `Either` it is early cutoff, 425 | for [`Logic`](https://hackage.haskell.org/package/logict-0.7.1.0) this means backtracking computation, etc. 426 | 427 | Again, don't worry about analogies and metaphors; focus on the API and the 428 | [laws](https://wiki.haskell.org/Monad_laws). 429 | 430 | > Hey, did you check the monad laws? left identity, right identity, and associativity? We've already 431 | > discussed a type class with exactly these laws - the `Monoid` type class. Maybe this is related 432 | > to the famous quote about monads being just monoids in something something... 433 | 434 | ### Do notation? 435 | 436 | Remember the [do notation](../05-glue/02-io.html#do-notation)? It turns out it works for any type that is 437 | an instance of `Monad`. How cool is that? Instead of writing: 438 | 439 | ```hs 440 | pipeline :: String -> Either Error TypedAST 441 | pipeline string = 442 | tokenize string >>= \tokens -> 443 | parse tokens >>= \ast -> 444 | typecheck ast 445 | ``` 446 | 447 | We can write: 448 | 449 | ```hs 450 | pipeline :: String -> Either Error TypedAST 451 | pipeline string = do 452 | tokens <- tokenize string 453 | ast <- parse tokens 454 | typecheck ast 455 | ``` 456 | 457 | And it will work! Still, in this particular case, `tokenize string >>= parse >>= typecheck` 458 | is so concise it can only be beaten by using 459 | [>=>](https://hackage.haskell.org/package/base-4.16.4.0/docs/Control-Monad.html#v:-62--61--62-) 460 | or 461 | [<=<](https://hackage.haskell.org/package/base-4.16.4.0/docs/Control-Monad.html#v:-60--61--60-): 462 | 463 | ```hs 464 | (>=>) :: Monad m => (a -> m b) -> (b -> m c) -> a -> m c 465 | (<=<) :: Monad m => (b -> m c) -> (a -> m b) -> a -> m c 466 | 467 | -- compare with function composition: 468 | (.) :: (b -> c) -> (a -> b) -> a -> c 469 | ``` 470 | 471 | ```hs 472 | pipeline = tokenize >=> parse >=> typecheck 473 | ``` 474 | 475 | or 476 | 477 | ```hs 478 | pipeline = typecheck <=< parse <=< tokenize 479 | ``` 480 | 481 | Haskell's ability to create very concise code using abstractions is 482 | great once one is familiar with the abstractions. Knowing the monad abstraction, 483 | we are now already familiar with the core composition API of many libraries - for example: 484 | 485 | - [Concurrent](https://hackage.haskell.org/package/stm) 486 | and [asynchronous programming](https://hackage.haskell.org/package/async) 487 | - [Web programming](https://gilmi.me/blog/post/2020/12/05/scotty-bulletin-board) 488 | - [Testing](http://hspec.github.io/) 489 | - [Emulating stateful computation](https://hackage.haskell.org/package/mtl-2.3.1/docs/Control-Monad-State-Lazy.html#g:2) 490 | - [sharing environment between computations](https://hackage.haskell.org/package/mtl-2.3.1/docs/Control-Monad-Reader.html#g:2) 491 | - and many more. 492 | 493 | ## Summary 494 | 495 | Using `Either` for error handling is useful for two reasons: 496 | 497 | 1. We encode possible errors using types, and we **force users to acknowledge and handle** them, thus 498 | making our code more resilient to crashes and bad behaviours 499 | 2. The `Functor`, `Applicative`, and `Monad` interfaces provide us with mechanisms for 500 | **composing** functions that might fail (almost) effortlessly - reducing boilerplate while 501 | maintaining strong guarantees about our code and delaying the need to handle errors until 502 | it is appropriate 503 | -------------------------------------------------------------------------------- /src/06-errors_and_files/02-except.md: -------------------------------------------------------------------------------- 1 | # Either with IO? 2 | 3 | When we create `IO` actions that may require I/O, we risk running into all kinds of errors. 4 | For example, when we use `writeFile`, we could run out of disk space in the middle of writing, 5 | or the file might be write-protected. While these scenarios aren't super common, they are definitely 6 | possible. 7 | 8 | We could've potentially encoded Haskell functions like `readFile` and `writeFile` as `IO` operations 9 | that return `Either`, for example: 10 | 11 | ```hs 12 | readFile :: FilePath -> IO (Either ReadFileError String) 13 | writeFile :: FilePath -> String -> IO (Either WriteFileError ()) 14 | ``` 15 | 16 | However, there are a couple of issues here; the first is that composing `IO` actions 17 | becomes more difficult. Previously we could write: 18 | 19 | ```hs 20 | readFile "input.txt" >>= writeFile "output.html" 21 | ``` 22 | 23 | But now the types no longer match - `readFile` will return an `Either ReadFileError String` when executed, 24 | but `writeFile` wants to take a `String` as input. We are forced to handle the error 25 | before calling `writeFile`. 26 | 27 | ## Composing IO + Either using ExceptT 28 | 29 | One way to handle this is by using **monad transformers**. Monad transformers provide a way 30 | to stack monad capabilities on top of one another. They are called transformers because 31 | **they take a type that has an instance of monad as input and return a new type that 32 | implements the monad interface, stacking a new capability on top of it**. 33 | 34 | For example, if we want to compose values of a type that is equivalent to `IO (Either Error a)`, 35 | using the monadic interface (the function `>>=`), we can use a monad transformer 36 | called [`ExceptT`](https://hackage.haskell.org/package/mtl-2.3.1/docs/Control-Monad-Except.html#g:2) 37 | and stack it over `IO`. 38 | Let's see how `ExceptT` is defined: 39 | 40 | ```hs 41 | newtype ExceptT e m a = ExceptT (m (Either e a)) 42 | ``` 43 | 44 | Remember, a `newtype` is a new name for an existing type. And if we substitute 45 | `e` with `Error` and `m` with `IO`, we get exactly `IO (Either Error a)` as we wanted. 46 | And we can convert an `ExceptT Error IO a` into `IO (Either Error a)` using 47 | the function `runExceptT`: 48 | 49 | ```hs 50 | runExceptT :: ExceptT e m a -> m (Either e a) 51 | ``` 52 | 53 | `ExceptT` implements the monadic interface in a way that combines the capabilities of 54 | `Either`, and whatever `m` it takes. Because `ExceptT e m` has a `Monad` instance, 55 | a specialized version of `>>=` would look like this: 56 | 57 | ```hs 58 | -- Generalized version 59 | (>>=) :: Monad m => m a -> (a -> m b) -> m b 60 | 61 | -- Specialized version, replace the `m` above with `ExceptT e m`. 62 | (>>=) :: Monad m => ExceptT e m a -> (a -> ExceptT e m b) -> ExceptT e m b 63 | ``` 64 | 65 | Note that the `m` in the specialized version still needs to be an instance of `Monad`. 66 | 67 | --- 68 | 69 | Unsure how this works? Try to implement `>>=` for `IO (Either Error a)`: 70 | 71 | ```hs 72 | bindExceptT :: IO (Either Error a) -> (a -> IO (Either Error b)) -> IO (Either Error b) 73 | ``` 74 | 75 |
      Solution 76 | 77 | ```hs 78 | bindExceptT :: IO (Either Error a) -> (a -> IO (Either Error b)) -> IO (Either Error b) 79 | bindExceptT mx f = do 80 | x <- mx -- `x` has the type `Either Error a` 81 | case x of 82 | Left err -> pure (Left err) 83 | Right y -> f y 84 | ``` 85 | 86 | Note that we didn't actually use the implementation details of `Error` or `IO`, 87 | `Error` isn't mentioned at all, and for `IO`, we only used the monadic interface with 88 | the do notation. We could write the same function with a more generalized type signature: 89 | 90 | ```hs 91 | bindExceptT :: Monad m => m (Either e a) -> (a -> m (Either e b)) -> m (Either e b) 92 | bindExceptT mx f = do 93 | x <- mx -- `x` has the type `Either e a` 94 | case x of 95 | Left err -> pure (Left err) 96 | Right y -> f y 97 | ``` 98 | 99 | And because `newtype ExceptT e m a = ExceptT (m (Either e a))`, we can just 100 | pack and unpack that `ExceptT` constructor and get: 101 | 102 | 103 | ```hs 104 | bindExceptT :: Monad m => ExceptT e m a -> (a -> ExceptT e m b) -> ExceptT e m b 105 | bindExceptT mx f = ExceptT $ do 106 | -- `runExceptT mx` has the type `m (Either e a)` 107 | -- `x` has the type `Either e a` 108 | x <- runExceptT mx 109 | case x of 110 | Left err -> pure (Left err) 111 | Right y -> runExceptT (f y) 112 | ``` 113 | 114 |
      115 | 116 | --- 117 | 118 | > Note that when stacking monad transformers, the order in which we stack them matters. 119 | > With `ExceptT Error IO a`, we have an `IO` operation that, when run will return `Either` 120 | > an error or a value. 121 | 122 | `ExceptT` can enjoy both worlds - we can return error values using the function `throwError`: 123 | 124 | ```hs 125 | throwError :: e -> ExceptT e m a 126 | ``` 127 | 128 | and we can "lift" functions that return a value of the underlying monadic type `m` to return 129 | a value of `ExceptT e m a` instead: 130 | 131 | ```hs 132 | lift :: m a -> ExceptT e m a 133 | ``` 134 | 135 | for example: 136 | 137 | ```hs 138 | getLine :: IO String 139 | 140 | lift getLine :: ExceptT e IO String 141 | ``` 142 | 143 | > Actually, `lift` is also a type class function from `MonadTrans`, the type class 144 | > of monad transformers. So technically, `lift getLine :: MonadTrans t => t IO String`, 145 | > but we are specializing for concreteness. 146 | 147 | 148 | Now, if we had: 149 | 150 | ```hs 151 | readFile :: FilePath -> ExceptT IOError IO String 152 | 153 | writeFile :: FilePath -> String -> ExceptT IOError IO () 154 | ``` 155 | 156 | We could compose them again without issue: 157 | 158 | ```hs 159 | readFile "input.txt" >>= writeFile "output.html" 160 | ``` 161 | 162 | But remember - the error type `e` (in both the case `Either` and `Except`) 163 | must be the same between composed functions! This means that the type representing 164 | errors for both `readFile` and `writeFile` must be the same - that would also 165 | force anyone using these functions to handle these errors - should a user who 166 | called `writeFile` be required to handle a "file not found" error? Should a user 167 | who called `readFile` be required to handle an "out of disk space" error? 168 | There are many, many more possible IO errors! "network unreachable", "out of memory", 169 | "cancelled thread", we cannot require a user to handle all these errors, or 170 | even cover them all in a data type. 171 | 172 | So what do we do? 173 | 174 | We give up on this approach **for IO code**, and use a different one: Exceptions, 175 | as we'll see in the next chapter. 176 | 177 | > Note - when we stack `ExceptT` on top of a different type called 178 | > [`Identity`](https://hackage.haskell.org/package/base-4.16.4.0/docs/Data-Functor-Identity.html) 179 | > that also implements the `Monad` interface, we get a type that is exactly like `Either` 180 | > called [`Except`](https://hackage.haskell.org/package/transformers-0.6.0.2/docs/Control-Monad-Trans-Except.html#t:Except) 181 | > (without the `T` at the end). You might sometimes want to use `Except` instead of `Either` 182 | > because it has a more appropriate name and better API for error handling than `Either`. 183 | -------------------------------------------------------------------------------- /src/06-errors_and_files/03-exceptions.md: -------------------------------------------------------------------------------- 1 | # Exceptions 2 | 3 | The [Control.Exception](https://hackage.haskell.org/package/base-4.16.4.0/docs/Control-Exception.html) 4 | module provides us with the ability to 5 | [throw](https://hackage.haskell.org/package/base-4.16.4.0/docs/Control-Exception.html#v:throwIO) 6 | exceptions from `IO` code, 7 | [`catch`](https://hackage.haskell.org/package/base-4.16.4.0/docs/Control-Exception.html#g:5) 8 | Haskell exceptions in `IO` code, and even convert them to `IO (Either ...)` 9 | with the function [`try`](https://hackage.haskell.org/package/base-4.16.4.0/docs/Control-Exception.html#g:7): 10 | 11 | ```hs 12 | throwIO :: Exception e => e -> IO a 13 | 14 | catch 15 | :: Exception e 16 | => IO a -- The computation to run 17 | -> (e -> IO a) -- Handler to invoke if an exception is raised 18 | -> IO a 19 | 20 | try :: Exception e => IO a -> IO (Either e a) 21 | ``` 22 | 23 | The important part of these type signatures is the 24 | [`Exception`](https://hackage.haskell.org/package/base-4.16.4.0/docs/Control-Exception.html#t:Exception) 25 | type class. By making a type an instance of the `Exception` type class, we can throw it 26 | and catch it in `IO` code: 27 | 28 | ```hs 29 | {-# language LambdaCase #-} 30 | 31 | import Control.Exception 32 | import System.IO 33 | 34 | data MyException 35 | = ErrZero 36 | | ErrOdd Int 37 | deriving Show 38 | 39 | instance Exception MyException 40 | 41 | sayDiv2 :: Int -> IO () 42 | sayDiv2 n 43 | | n == 0 = throwIO ErrZero 44 | | n `mod` 2 /= 0 = throwIO (ErrOdd n) 45 | | otherwise = print (n `div` 2) 46 | 47 | main :: IO () 48 | main = 49 | catch 50 | ( do 51 | putStrLn "Going to print a number now." 52 | sayDiv2 7 53 | putStrLn "Did you like it?" 54 | ) 55 | ( \case 56 | ErrZero -> 57 | hPutStrLn stderr "Error: we don't support dividing zeroes for some reason" 58 | ErrOdd n -> 59 | hPutStrLn stderr ("Error: " <> show n <> " is odd and cannot be divided by 2") 60 | ) 61 | ``` 62 | 63 | > Note: we are using two new things here: guards and the `LambdaCase` language extension. 64 | > 65 | > 1. Guards, as seen in `sayDiv2` are just a nicer syntax around `if-then-else` expressions. 66 | > Using guards, we can have multiple `if` branches and finally use the `else` branch 67 | > by using `otherwise`. After each guard (`|`), there's a condition; after the condition, there's 68 | > a `=` and then the expression (the part after `then` in an `if` expression) 69 | > 70 | > 2. LambdaCase, as seen in `catch`, is just a syntactic sugar to save a few characters, 71 | > instead of writing `\e -> case e of`, we can write `\case`. It requires enabling the 72 | > `LambdaCase` extension 73 | > 74 | > #### Language extensions 75 | > 76 | > Haskell is a standardized language. However, GHC provides *extensions* to the language - 77 | > additional features that aren't covered in the 98 or 2010 standards of Haskell. 78 | > Features such as syntactic extensions (like LambdaCase above), extensions to the type checker, 79 | > and more. 80 | > 81 | > These extensions can be added by adding `{-# language #-}` 82 | > (the `language` part is case insensitive) 83 | > to the top of a Haskell source file, or they can be set globally for an entire project by 84 | > specifying them in the 85 | > [default-extensions](https://cabal.readthedocs.io/en/stable/cabal-package.html#pkg-field-default-extensions) 86 | > section in the `.cabal file`. 87 | > 88 | > The list of language extensions can be found in the 89 | > [GHC manual](https://ghc.gitlab.haskell.org/ghc/doc/users_guide/exts.html), 90 | > feel free to browse it, but don't worry about trying to memorize all the extensions. 91 | 92 | 93 | Of course, this example would work much better using `Either` and separating 94 | the division and printing à la 'functional core, imperative shell'. But as an example, it works. 95 | We created a custom exception and handled it specifically outside an `IO` block. 96 | However, we have not handled exceptions that might be raised by `putStrLn`. 97 | What if, for example, for some reason, we close the `stdout` handle before this block: 98 | 99 | ```hs 100 | main :: IO () 101 | main = do 102 | hClose stdout 103 | catch 104 | ( do 105 | putStrLn "Going to print a number now." 106 | sayDiv2 7 107 | putStrLn "Did you like it?" 108 | ) 109 | ( \case 110 | ErrZero -> 111 | hPutStrLn stderr "Error: we don't support dividing zeroes for some reason" 112 | ErrOdd n -> 113 | hPutStrLn stderr ("Error: " <> show n <> " is odd and cannot be divided by 2") 114 | ) 115 | ``` 116 | 117 | Our program will crash with an error: 118 | 119 | ``` 120 | ghc: : hFlush: illegal operation (handle is closed) 121 | ``` 122 | 123 | First, how do we know which exception we should handle? Some functions' documentation 124 | include this, but unfortunately, `putStrLn`'s does not. We could guess from the 125 | [list of instances](https://hackage.haskell.org/package/base-4.16.4.0/docs/Control-Exception.html#i:Exception) 126 | the `Exception` type class has; I think 127 | [`IOException`](https://hackage.haskell.org/package/base-4.16.4.0/docs/GHC-IO-Exception.html#t:IOException) fits. Now, how can we handle this case as well? We can chain catches: 128 | 129 | ```hs 130 | -- need to add these at the top 131 | 132 | {-# language ScopedTypeVariables #-} 133 | 134 | import GHC.IO.Exception (IOException(..)) 135 | 136 | main :: IO () 137 | main = do 138 | hClose stdout 139 | catch 140 | ( catch 141 | ( do 142 | putStrLn "Going to print a number now." 143 | sayDiv2 7 144 | putStrLn "Did you like it?" 145 | ) 146 | ( \case 147 | ErrZero -> 148 | hPutStrLn stderr "Error: we don't support dividing zeroes for some reason" 149 | ErrOdd n -> 150 | hPutStrLn stderr ("Error: " <> show n <> " is odd and cannot be divided by 2") 151 | ) 152 | ) 153 | ( \(e :: IOException) -> 154 | -- we can check if the error was an illegal operation on the stderr handle 155 | if ioe_handle e /= Just stderr && ioe_type e /= IllegalOperation 156 | then pure () -- we can't write to stderr because it is closed 157 | else hPutStrLn stderr (displayException e) 158 | ) 159 | ``` 160 | 161 | > We use the `ScopedTypeVariables` to be able to specify types inside let expressions, 162 | > lambdas, pattern matching, and more. 163 | 164 | Or we could use the convenient function 165 | [`catches`](https://hackage.haskell.org/package/base-4.16.4.0/docs/Control-Exception.html#v:catches) 166 | to pass a list of exception 167 | [handlers](https://hackage.haskell.org/package/base-4.16.4.0/docs/Control-Exception.html#t:Handler): 168 | 169 | ```hs 170 | main :: IO () 171 | main = do 172 | hClose stdout 173 | catches 174 | ( do 175 | putStrLn "Going to print a number now." 176 | sayDiv2 7 177 | putStrLn "Did you like it?" 178 | ) 179 | [ Handler $ \case 180 | ErrZero -> 181 | hPutStrLn stderr "Error: we don't support dividing zeroes for some reason" 182 | ErrOdd n -> 183 | hPutStrLn stderr ("Error: " <> show n <> " is odd and cannot be divided by 2") 184 | 185 | , Handler $ \(e :: IOException) -> 186 | -- we can check if the error was an illegal operation on the stderr handle 187 | if ioe_handle e /= Just stderr && ioe_type e /= IllegalOperation 188 | then pure () -- we can't write to stderr because it is closed 189 | else hPutStrLn stderr (displayException e) 190 | ] 191 | ``` 192 | 193 | > As an aside, `Handler` uses a concept called 194 | > [existentially quantified types](https://en.m.wikibooks.org/wiki/Haskell/Existentially_quantified_types) 195 | > to hide inside it a function that takes an arbitrary type that implements `Exception`. 196 | > This is why we can encode a seemingly heterogeneous list of functions that handle exceptions 197 | > for `catches` to take as input. 198 | > This pattern is rarely useful, but I've included it here to avoid confusion. 199 | 200 | And if we wanted to catch any exception, we'd catch `SomeException`: 201 | 202 | ```hs 203 | main :: IO () 204 | main = do 205 | hClose stdout 206 | catch 207 | ( do 208 | putStrLn "Going to print a number now." 209 | sayDiv2 7 210 | putStrLn "Did you like it?" 211 | ) 212 | ( \(SomeException e) -> 213 | hPutStrLn stderr (show e) 214 | ) 215 | ``` 216 | 217 | This could also go in `catches` as the last element in the list if we wanted specialized 218 | handling for other scenarios. 219 | 220 | A couple more functions worth knowing are 221 | [`bracket`](https://hackage.haskell.org/package/base-4.16.4.0/docs/Control-Exception.html#v:bracket) 222 | and [`finally`](https://hackage.haskell.org/package/base-4.16.4.0/docs/Control-Exception.html#v:finally). 223 | These functions can help us handle resource acquisition more safely when errors are present. 224 | 225 | --- 226 | 227 | In our `main` in the `app/Main.hs` file, we do a small ritual of opening and closing handles. 228 | Are there scenarios where we would clean-up after ourselves (meaning, close handles we've 229 | opened)? Which parts of the code could throw an exception? Which handles won't get closed? 230 | 231 | - Try to use `bracket` to make sure we always close a handle afterward, even if an exception 232 | is thrown, and avoid closing the handle for the `stdin` and `stdout` cases 233 |
      HintWe might need to use continuation-passing style, 234 | passing a function that takes a parameter to a function that produces a parameter 235 | and calls it with that parameter. 236 |
      237 | - How can we avoid duplicating the `outputHandle` code for the `Stdin` and `InputFile` 238 | branches?
      Hint Use `let`.
      239 | 240 |
      Answer 241 | 242 | ```hs 243 | import Control.Exception (bracket) 244 | 245 | main :: IO () 246 | main = do 247 | ... 248 | 249 | ConvertSingle input output -> 250 | let 251 | -- Here, action is the next steps we want to do. 252 | -- It takes as input the values we produce, 253 | -- uses it, and then returns control for us to clean-up 254 | -- afterwards. 255 | withInputHandle :: (String -> Handle -> IO a) -> IO a 256 | withInputHandle action = 257 | case input of 258 | Stdin -> 259 | action "" stdin 260 | InputFile file -> 261 | bracket 262 | (openFile file ReadMode) 263 | hClose 264 | (action file) 265 | 266 | -- Note that in both functions our action can return any `a` 267 | -- it wants. 268 | withOutputHandle :: (Handle -> IO a) -> IO a 269 | withOutputHandle action = 270 | case output of 271 | Stdout -> 272 | action stdout 273 | OutputFile file -> do 274 | exists <- doesFileExist file 275 | shouldOpenFile <- 276 | if exists 277 | then confirm 278 | else pure True 279 | if shouldOpenFile 280 | then 281 | bracket (openFile file WriteMode) hClose action 282 | else 283 | exitFailure 284 | in 285 | withInputHandle (\title -> withOutputHandle . HsBlog.convertSingle title) 286 | ``` 287 | 288 |
      289 | 290 | There's actually a custom function that does a similar thing to 291 | `bracket (openFile file ) hClose`, it's called 292 | [withFile](https://hackage.haskell.org/package/base-4.17.0.0/docs/System-IO.html#v:withFile). 293 | Keep an eye out for functions that start with the prefix `with`; they are probably using the 294 | same pattern of continuation-passing style. 295 | 296 | --- 297 | 298 | ## Summary 299 | 300 | Exceptions are useful and often necessary when we work with `IO` and want to make sure 301 | our program is handling errors gracefully. They have an advantage over `Either` in that 302 | we can easily compose functions that may throw errors of different types, but also have 303 | a disadvantage of not encoding types as return values, and therefore does not force us 304 | to handle them. 305 | 306 | For Haskell, the language designers have made a choice for us by designing `IO` to 307 | use exceptions instead of `Either`. And this is what I would recommend for 308 | handling your own effectful computations. However, I think that `Either` is more 309 | appropriate for uneffectful code, because it forces us to acknowledge and handle errors 310 | (eventually), thus making our programs more robust. And also because we can only 311 | catch exceptions in `IO` code. 312 | -------------------------------------------------------------------------------- /src/06-errors_and_files/05-summary.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | This was quite a section. Let's recount the things we've learned. 4 | 5 | We discussed several ways to handle errors in Haskell: 6 | 7 | 1. Encoding errors as a data type and using the `Either` type to encode "a value or an error". 8 | Useful approach for uneffectful code 9 | 2. Using `ExceptT` when we want to combine the approach in (1) on top of an existing 10 | type with monadic capabilities 11 | 3. Using exceptions for IO code 12 | 13 | We've also learned a few new abstractions and techniques: 14 | 15 | 1. The `Traversable` type class, for data structures that can be traversed from left to right, 16 | such as linked lists, binary trees and `Map`s. 17 | Pretty useful when combined with another applicative functor type like `Either` or `IO` 18 | 2. The `Monad` type class extends the `Applicative` type class with the `join :: m (m a) -> m a` 19 | function. We learned that `Either` implements this type class interface, and so does `IO` 20 | 3. The `MonadTrans` type class for *monad transformers* for types that take other monads as inputs 21 | and provide a monadic interface (`>>=`, do notation, etc.) while combining both their capabilities. 22 | We saw how to stack an `Either`-like monad transformer, `ExceptT`, on top of `IO` 23 | 24 | We are almost done – only a couple more things left to do with this project. Let's go! 25 | 26 | > You can view the git commit of 27 | > [the changes we've made](https://github.com/soupi/learn-haskell-blog-generator/commit/a08d148d981fa00cb7025f1b651d7b75084dd1ae) 28 | > and the [code up until now](https://github.com/soupi/learn-haskell-blog-generator/tree/a08d148d981fa00cb7025f1b651d7b75084dd1ae). 29 | -------------------------------------------------------------------------------- /src/08-testing.md: -------------------------------------------------------------------------------- 1 | # Testing 2 | 3 | We want to add some tests to our blog generator. At the very least 4 | a few regression tests to make sure that if we extend or change our markup parsing code, 5 | HTML generation code, or translation from markup to HTML code, and make a mistake, we'll 6 | have a safety net alerting us of issues. 7 | 8 | We will use the [Hspec](https://hspec.github.io/) testing framework to write our tests. 9 | There are other testing frameworks in Haskell, for example, 10 | [tasty](https://hackage.haskell.org/package/tasty), but I like Hspec's documentation, 11 | so we'll use that. 12 | 13 | ## Initial setup 14 | 15 | ### Cabal file additions 16 | 17 | We're going to define a new section in our `hs-blog-gen.cabal` file for our new test suite. 18 | This section is called `test-suite` and is fairly similar to the `library` and 19 | `executable` sections. 20 | 21 | The interfaces for how to define a test suite are described in the 22 | [Cabal documentation](https://cabal.readthedocs.io/en/stable/cabal-package.html#test-suites). 23 | We are going to use the `exitcode-stdio-1.0` interface. Let's go over the different settings 24 | and options: 25 | 26 | ```cabal 27 | test-suite hs-blog-gen-test 28 | import: common-settings 29 | type: exitcode-stdio-1.0 30 | hs-source-dirs: test 31 | main-is: Spec.hs 32 | 33 | -- other-modules: 34 | build-depends: 35 | base 36 | , hspec 37 | , hspec-discover 38 | , raw-strings-qq 39 | , hs-blog 40 | ghc-options: 41 | -O -threaded -rtsopts -with-rtsopts=-N 42 | build-tool-depends: 43 | hspec-discover:hspec-discover 44 | ``` 45 | 46 | - `hs-source-dirs: test` - The directory of the source files for the test suite 47 | - `main-is: Spec.hs` - The entry point to the test suite 48 | - `other-modules` - The modules in our test suite. 49 | Currently commented out because we haven't added any yet 50 | - `build-depends` - The packages we are going to use: 51 | - [`base`](https://hackage.haskell.org/package/base) - 52 | The standard library for Haskell, as we've used before 53 | - [`hspec`](https://hackage.haskell.org/package/hspec) - 54 | The test framework we are going to use 55 | - [`hspec-discover`](https://hackage.haskell.org/package/hspec-discover) - 56 | Automatic discovery of Hspec tests 57 | - [`raw-strings-qq`](https://hackage.haskell.org/package/raw-strings-qq) - 58 | Additional syntax for writing raw string literals 59 | - `hs-blog` - Our library 60 | - [`ghc-options`](https://cabal.readthedocs.io/en/stable/cabal-package.html#pkg-field-ghc-options) - 61 | Extra options and flags for GHC: 62 | - [`-O`](https://downloads.haskell.org/ghc/latest/docs/html/users_guide/using-optimisation.html#options-optimise) - 63 | Compile with optimizations 64 | - [`-threaded`](https://downloads.haskell.org/ghc/latest/docs/html/users_guide/phases.html#ghc-flag--threaded) - 65 | Use the multi-core runtime instead of the single-core runtime. The multi-core 66 | runtime is generally a bit slower in my experience, but when writing code that actually uses 67 | multiple cores (such as a test framework that runs tests in parallel), it can give a good 68 | performance boost 69 | - [`-rtsopts`](https://downloads.haskell.org/ghc/latest/docs/html/users_guide/phases.html#ghc-flag--rtsopts[=%E2%9F%A8none|some|all|ignore|ignoreAll%E2%9F%A9]) - 70 | Let us configure the Haskell runtime system by passing command-line arguments to our application 71 | - [`-with-rtsopts=-N`](https://downloads.haskell.org/ghc/latest/docs/html/users_guide/phases.html#ghc-flag--with-rtsopts=%E2%9F%A8opts%E2%9F%A9) - 72 | Set specific default options for the program at link-time. 73 | Specifically, [`-N`](https://downloads.haskell.org/ghc/latest/docs/html/users_guide/using-concurrent.html#rts-flag--N%20%E2%9F%A8x%E2%9F%A9) 74 | Sets the number of cores to use in our program 75 | - [`build-tool-depends`](https://cabal.readthedocs.io/en/stable/cabal-package.html#pkg-field-build-tool-depends) - 76 | Use a specific executable from a package dependency in aid of building the package. 77 | In this case, we are using the `hspec-discover` executable from the 78 | [`hspec-discover`](https://hackage.haskell.org/package/hspec-discover) package, which 79 | goes over the source directory for the tests, finds all of the `Spec` files 80 | and creates an entry point for the program that will run all the tests it discovered 81 | 82 | 83 | ### Hspec discovery 84 | 85 | For `hspec-discover` to work, we need to add the following 86 | to the "main" file of the test suite, for us, this is `test/Spec.hs`: 87 | 88 | ```hs 89 | {-# OPTIONS_GHC -F -pgmF hspec-discover #-} 90 | ``` 91 | 92 | That's it! `hspec-discover` will automatically define a `main` for us. 93 | Now we can run the tests using `stack test` or `cabal test` (your choice). 94 | Because we haven't defined any tests, our output is: 95 | 96 | ```sh 97 | Finished in 0.0000 seconds 98 | 0 examples, 0 failures 99 | ``` 100 | 101 | When we add new Hspec tests, `hspec-discover` will find and run them automatically 102 | (though we will still need to add them to the `other-modules` section in the cabal file). 103 | 104 | For `hspec-discover` to identify modules as test modules, the modules must follow 105 | a convention: 106 | 107 | 1. Their module names must end with `Spec` 108 | 2. They must define a value `spec :: Spec` (which describes the test) and export it 109 | outside of the module (by adding it to the export list of the module, for example) 110 | 111 | ## Writing tests 112 | 113 | Let's write our first test. We'll create a new module to test 114 | markup parsing. We'll call it `MarkupParsingSpec.hs`. We'll need 115 | the following imports as well: 116 | 117 | ```hs 118 | module MarkupParsingSpec where 119 | 120 | import Test.Hspec 121 | import HsBlog.Markup 122 | ``` 123 | 124 | `Hspec` provides us with a monadic interface for describing, composing and 125 | nesting test specifications (`Spec`s). 126 | 127 | Using the `describe` function, we can 128 | describe a group of tests; using the `it` function, we can add a new test, 129 | and using a function like `shouldBe`, we can compare two values and make 130 | sure they are equal by using their `Eq` instance. 131 | If they are, the test will pass; if not, it will fail with a descriptive error. 132 | 133 | Let's try it and write a test that obviously fails! 134 | 135 | ```hs 136 | spec :: Spec 137 | spec = do 138 | describe "Markup parsing tests" $ do 139 | it "empty" $ 140 | shouldBe 141 | (parse "") 142 | [Heading 1 "bug"] 143 | ``` 144 | 145 | After adding the module to the `other-modules` list in the cabal file: 146 | 147 | ```hs 148 | other-modules: 149 | MarkupParsingSpec 150 | ``` 151 | 152 | And running the tests, we get this output: 153 | 154 | ```hs 155 | MarkupParsing 156 | Markup parsing tests 157 | empty FAILED [1] 158 | 159 | Failures: 160 | 161 | test/MarkupParsingSpec.hs:10:7: 162 | 1) MarkupParsing, Markup parsing tests, empty 163 | expected: [Heading 1 "bug"] 164 | but got: [] 165 | 166 | To rerun use: --match "/MarkupParsing/Markup parsing tests/empty/" 167 | 168 | Randomized with seed 763489823 169 | 170 | Finished in 0.0004 seconds 171 | 1 example, 1 failure 172 | ``` 173 | 174 | The output describes which tests are running in a hierarchy tree (module, group, and test), 175 | whether the tests pass or fail, and if they fail, the output and the expected output. 176 | 177 | We can fix our test by matching the expected output: 178 | 179 | ```hs 180 | shouldBe 181 | (parse "") 182 | [] 183 | ``` 184 | 185 | Now, running the tests will produce: 186 | 187 | ```hs 188 | MarkupParsing 189 | Markup parsing tests 190 | empty 191 | 192 | Finished in 0.0001 seconds 193 | 1 example, 0 failures 194 | ``` 195 | 196 | We can add a few more tests: 197 | 198 | ```hs 199 | it "paragraph" $ 200 | shouldBe 201 | (parse "hello world") 202 | [Paragraph "hello world"] 203 | 204 | it "heading 1" $ 205 | shouldBe 206 | (parse "* Heading 1") 207 | [Heading 1 "Heading 1"] 208 | 209 | it "code" $ 210 | shouldBe 211 | (parse "> main = putStrLn \"hello world!\"") 212 | [CodeBlock ["main = putStrLn \"hello world!\""]] 213 | ``` 214 | 215 | And run the tests again: 216 | 217 | ```sh 218 | MarkupParsing 219 | Markup parsing tests 220 | Test empty 221 | paragraph 222 | heading 1 223 | code 224 | 225 | Finished in 0.0003 seconds 226 | 4 examples, 0 failures 227 | ``` 228 | 229 | This is the gist of writing unit tests with Hspec. It's important to note 230 | that we can nest `Spec`s that are declared with `describe` to create trees, 231 | and, of course, refactor and move things to different functions and modules 232 | to make our test suite better organized. 233 | 234 | For example, we can write our tests like this: 235 | 236 | ```hs 237 | spec :: Spec 238 | spec = do 239 | describe "Markup parsing tests" $ do 240 | simple 241 | 242 | simple :: Spec 243 | simple = do 244 | describe "simple" $ do 245 | it "empty" $ 246 | shouldBe 247 | (parse "") 248 | [] 249 | 250 | it "paragraph" $ 251 | shouldBe 252 | (parse "hello world") 253 | [Paragraph "hello world"] 254 | 255 | it "heading 1" $ 256 | shouldBe 257 | (parse "* Heading 1") 258 | [Heading 1 "Heading 1"] 259 | 260 | it "code" $ 261 | shouldBe 262 | (parse "> main = putStrLn \"hello world!\"") 263 | [CodeBlock ["main = putStrLn \"hello world!\""]] 264 | ``` 265 | 266 | Also, there are other "expectations" like `shouldBe` that we can use when writing tests. 267 | They are described in the [Hspec tutorial](https://hspec.github.io/expectations.html) 268 | and can be found in the 269 | [haddock documentation](https://hackage.haskell.org/package/hspec-expectations-0.8.2/docs/Test-Hspec-Expectations.html) as well. 270 | 271 | ### Raw strings 272 | 273 | If we want to write multi-line strings or avoid escaping strings as we did in the "code" 274 | test, we can use a library called 275 | [raw-strings-qq](https://hackage.haskell.org/package/raw-strings-qq) 276 | which uses a language extension called 277 | [`QuasiQuotes`](https://downloads.haskell.org/ghc/latest/docs/html/users_guide/exts/template_haskell.html#extension-QuasiQuotes). 278 | `QuasiQuotes` is a meta-programming extension that provides a mechanism for extending the 279 | syntax of Haskell. 280 | 281 | A quasi-quote has the form `[quoter| string |]`, where the quoter is the name 282 | of the function providing the syntax we wish to use, and the string is our input. 283 | 284 | In our case, we use the quoter `r`, which is defined in 285 | [raw-strings-qq](https://hackage.haskell.org/package/raw-strings-qq-1.1/docs/Text-RawString-QQ.html), 286 | and write any string we want, with multi-lines and unescaped characters! 287 | We could use this to write the tests 288 | [we previously wrote](04-markup/01-data_type.html#exercises): 289 | 290 | ```hs 291 | {-# language QuasiQuotes #-} 292 | 293 | ... 294 | 295 | import Text.RawString.QQ 296 | 297 | ... 298 | 299 | example3 :: String 300 | example3 = [r| 301 | Remember that multiple lines with no separation 302 | are grouped together into a single paragraph 303 | but list items remain separate. 304 | 305 | # Item 1 of a list 306 | # Item 2 of the same list 307 | |] 308 | ``` 309 | 310 | And add multi-line tests: 311 | 312 | ```hs 313 | spec :: Spec 314 | spec = do 315 | describe "Markup parsing tests" $ do 316 | simple 317 | multiline 318 | 319 | 320 | multiline :: Spec 321 | multiline = do 322 | describe "Multi-line tests" $ do 323 | it "example3" $ 324 | shouldBe 325 | (parse example3) 326 | example3Result 327 | 328 | 329 | example3 :: String 330 | example3 = [r| 331 | Remember that multiple lines with no separation 332 | are grouped together into a single paragraph 333 | but list items remain separate. 334 | 335 | # Item 1 of a list 336 | # Item 2 of the same list 337 | |] 338 | 339 | example3Result :: Document 340 | example3Result = 341 | [ Paragraph "Remember that multiple lines with no separation are grouped together into a single paragraph but list items remain separate." 342 | , OrderedList 343 | [ "Item 1 of a list" 344 | , "Item 2 of the same list" 345 | ] 346 | ] 347 | ``` 348 | 349 | Running the tests: 350 | 351 | ```hs 352 | MarkupParsing 353 | Markup parsing tests 354 | simple 355 | Test empty 356 | paragraph 357 | heading 1 358 | code 359 | Multi-line tests 360 | example3 361 | 362 | Finished in 0.0004 seconds 363 | 5 examples, 0 failures 364 | ``` 365 | 366 | --- 367 | 368 | **Exercise**: Add a test for the fourth example described in the 369 | [previous exercises](04-markup/01-data_type.html#exercises). 370 | 371 | 372 |
      Solution 373 | 374 | ```hs 375 | multiline :: Spec 376 | multiline = do 377 | describe "Multi-line tests" $ do 378 | it "example3" $ 379 | shouldBe 380 | (parse example3) 381 | example3Result 382 | 383 | it "example4" $ 384 | shouldBe 385 | (parse example4) 386 | example4Result 387 | 388 | 389 | example4 :: String 390 | example4 = [r| 391 | * Compiling programs with ghc 392 | 393 | Running ghc invokes the Glasgow Haskell Compiler (GHC), 394 | and can be used to compile Haskell modules and programs into native 395 | executables and libraries. 396 | 397 | Create a new Haskell source file named hello.hs, and write 398 | the following code in it: 399 | 400 | > main = putStrLn "Hello, Haskell!" 401 | 402 | Now, we can compile the program by invoking ghc with the file name: 403 | 404 | > ➜ ghc hello.hs 405 | > [1 of 1] Compiling Main ( hello.hs, hello.o ) 406 | > Linking hello ... 407 | 408 | GHC created the following files: 409 | 410 | - hello.hi - Haskell interface file 411 | - hello.o - Object file, the output of the compiler before linking 412 | - hello (or hello.exe on Microsoft Windows) - A native runnable executable. 413 | 414 | GHC will produce an executable when the source file satisfies both conditions: 415 | 416 | # Defines the main function in the source file 417 | # Defines the module name to be Main or does not have a module declaration 418 | 419 | Otherwise, it will only produce the .o and .hi files. 420 | |] 421 | 422 | example4Result :: Document 423 | example4Result = 424 | [ Heading 1 "Compiling programs with ghc" 425 | , Paragraph "Running ghc invokes the Glasgow Haskell Compiler (GHC), and can be used to compile Haskell modules and programs into native executables and libraries." 426 | , Paragraph "Create a new Haskell source file named hello.hs, and write the following code in it:" 427 | , CodeBlock 428 | [ "main = putStrLn \"Hello, Haskell!\"" 429 | ] 430 | , Paragraph "Now, we can compile the program by invoking ghc with the file name:" 431 | , CodeBlock 432 | [ "➜ ghc hello.hs" 433 | , "[1 of 1] Compiling Main ( hello.hs, hello.o )" 434 | , "Linking hello ..." 435 | ] 436 | , Paragraph "GHC created the following files:" 437 | , UnorderedList 438 | [ "hello.hi - Haskell interface file" 439 | , "hello.o - Object file, the output of the compiler before linking" 440 | , "hello (or hello.exe on Microsoft Windows) - A native runnable executable." 441 | ] 442 | , Paragraph "GHC will produce an executable when the source file satisfies both conditions:" 443 | , OrderedList 444 | [ "Defines the main function in the source file" 445 | , "Defines the module name to be Main or does not have a module declaration" 446 | ] 447 | , Paragraph "Otherwise, it will only produce the .o and .hi files." 448 | ] 449 | ``` 450 | 451 |
      452 | 453 | --- 454 | 455 | ## Parallel test execution 456 | 457 | Without further configuration, Hspec will run all 458 | of our tests on the main thread sequentially. 459 | 460 | There are a couple of ways to configure tests to run 461 | in parallel. One is to manually mark a `Spec` 462 | as parallel by passing it to the `parallel` function, 463 | and another is by creating a /hook/ that will apply 464 | `parallel` to each `Spec` automatically with 465 | `hspec-discover`. 466 | 467 | Consult the [Hspec manual](https://hspec.github.io/parallel-spec-execution.html#running-all-tests-in-parallel-with-hspec-discover) 468 | on this topic and try both methods. Remember that 469 | we already enabled the threaded runtime and set it to 470 | use multiple cores in the cabal file. 471 | 472 | ## Summary 473 | 474 | This chapter has been just the tip of the iceberg of the Haskell testing landscape. 475 | We haven't talked about 476 | [property testing](https://www.scs.stanford.edu/16wi-cs240h/slides/testing.html) or 477 | [golden testing](https://ro-che.info/articles/2017-12-04-golden-tests), 478 | testing expected failures, testing IO code, inspection testing, benchmarking, and more. 479 | There's just too much to cover! 480 | 481 | I hope this chapter 482 | provided you with the basics of how to start writing tests for your projects. 483 | Please consult the tutorial for your chosen testing framework, and read more about 484 | this very important subject on your own. 485 | 486 | > You can view the git commit of 487 | > [the changes we've made](https://github.com/soupi/learn-haskell-blog-generator/commit/da1615b6e0a2a4ff2728528240d790754853bf02) 488 | > and the [code up until now](https://github.com/soupi/learn-haskell-blog-generator/tree/da1615b6e0a2a4ff2728528240d790754853bf02). 489 | -------------------------------------------------------------------------------- /src/09-documentation.md: -------------------------------------------------------------------------------- 1 | # Generating documentation 2 | 3 | There are [many ways](https://documentation.divio.com/) 4 | to help others to get started with our projects and libraries. 5 | For example, we can write tutorials, provide runnable examples, 6 | describe the system's internals, and create an API reference. 7 | 8 | In this chapter, we will focus on generating API reference pages (the kind that can be seen on Hackage) 9 | from annotated Haskell source code using [Haddock](https://haskell-haddock.readthedocs.io/en/latest/). 10 | 11 | ## Running Haddock 12 | 13 | We can generate API reference pages (a.k.a. haddocks in the Haskell world) for our project 14 | using our favorite package manager: 15 | 16 | ### Cabal 17 | 18 | We can run `cabal haddock` to generate haddocks: 19 | 20 | ```sh 21 | ➜ cabal haddock 22 | Resolving dependencies... 23 | Build profile: -w ghc-9.0.1 -O1 24 | In order, the following will be built (use -v for more details): 25 | - hs-blog-0.1.0.0 (lib) (first run) 26 | Configuring library for hs-blog-0.1.0.0.. 27 | Preprocessing library for hs-blog-0.1.0.0.. 28 | Running Haddock on library for hs-blog-0.1.0.0.. 29 | Haddock coverage: 30 | 0% ( 0 / 3) in 'HsBlog.Env' 31 | Missing documentation for: 32 | Module header 33 | Env (src/HsBlog/Env.hs:3) 34 | defaultEnv (src/HsBlog/Env.hs:10) 35 | 21% ( 7 / 33) in 'HsBlog.Html.Internal' 36 | Missing documentation for: 37 | Module header 38 | Html (src/HsBlog/Html/Internal.hs:8) 39 | ... 40 | Documentation created: 41 | /tmp/learn-haskell-blog-generator/dist-newstyle/build/x86_64-linux/ghc-9.0.1/hs-blog-0.1.0.0/doc/html/hs-blog/index.html 42 | ``` 43 | 44 | Cabal and Haddock will build our project and generate HTML pages for us at: 45 | 46 | ```html 47 | ./dist-newstyle/build///-/doc/html// 48 | ``` 49 | 50 | We can then open the `index.html` file from that directory in a web browser and view our package documentation. 51 | 52 | ### Stack 53 | 54 | We can run `stack haddock` to generate haddocks: 55 | 56 | ```sh 57 | ➜ stack haddock 58 | ... 59 | hs-blog> build (lib + exe) 60 | Preprocessing library for hs-blog-0.1.0.0.. 61 | Building library for hs-blog-0.1.0.0.. 62 | [1 of 7] Compiling HsBlog.Env 63 | [2 of 7] Compiling HsBlog.Html.Internal 64 | ... 65 | hs-blog> haddock 66 | Preprocessing library for hs-blog-0.1.0.0.. 67 | Running Haddock on library for hs-blog-0.1.0.0.. 68 | Haddock coverage: 69 | 0% ( 0 / 3) in 'HsBlog.Env' 70 | Missing documentation for: 71 | Module header 72 | Env (src/HsBlog/Env.hs:3) 73 | defaultEnv (src/HsBlog/Env.hs:10) 74 | 21% ( 7 / 33) in 'HsBlog.Html.Internal' 75 | Missing documentation for: 76 | Module header 77 | Html (src/HsBlog/Html/Internal.hs:8) 78 | ... 79 | Documentation created: 80 | .stack-work/dist/x86_64-linux-tinfo6/Cabal-3.2.1.0/doc/html/hs-blog/index.html, 81 | .stack-work/dist/x86_64-linux-tinfo6/Cabal-3.2.1.0/doc/html/hs-blog/hs-blog.txt 82 | Preprocessing executable 'hs-blog-gen' for hs-blog-0.1.0.0.. 83 | ... 84 | ``` 85 | 86 | Stack and Haddock will build our project and generate HTML pages for us at: 87 | 88 | ```html 89 | ./.stack-work/dist//Cabal-/doc/html// 90 | ``` 91 | 92 | We can then open the `index.html` file from that directory in a web browser and view our package documentation. 93 | 94 | ### Haddock coverage 95 | 96 | Haddock will also output a coverage report when run and mention user-exposed constructs that are missing 97 | documentation. These constructs could be module headers, types, data constructors, type classes, functions, values, etc. 98 | 99 | For example: 100 | 101 | ```hs 102 | Haddock coverage: 103 | ... 104 | 0% ( 0 / 3) in 'HsBlog.Convert' 105 | Missing documentation for: 106 | Module header 107 | convert (src/HsBlog/Convert.hs:8) 108 | convertStructure (src/HsBlog/Convert.hs:23) 109 | 67% ( 2 / 3) in 'HsBlog.Directory' 110 | Missing documentation for: 111 | buildIndex (src/HsBlog/Directory.hs:80) 112 | ... 113 | ``` 114 | 115 | We can see that we did not document the `HsBlog.Convert` at all, and we are missing 116 | documentation for the module header, the `convert` function, and the `convertStructure` function. 117 | 118 | On the other hand, it seems that we do currently have some documentation written for the `HsBlog.Directory` 119 | module! We'll see why, but first - try to generate haddocks, visit the module hierarchy, browse around 120 | the different modules, follow the links of the types, imagine what this API reference could look like, 121 | and let's see how we can improve it. 122 | 123 | ## Haddock markup 124 | 125 | Haddock builds the API reference pages by building our project, examining the exported modules 126 | and their exported definitions, and grabbing source code comments written in special markup format. 127 | 128 | Let's take a quick look at this markup format. We will go over a few important bits, 129 | but if you'd like to learn more, a complete guide for Haddock markup can be found in the 130 | [Haddock documentation](https://haskell-haddock.readthedocs.io/en/latest/markup.html). 131 | 132 | 133 | ### Documenting definitions 134 | 135 | All haddock annotations appear as part of regular Haskell comments. 136 | They can be used with both single-line form (`--`) and multi-line form (`{-` and `-}`). 137 | The placements of a comment block and the haddock marker determine which Haskell 138 | definition the haddock string is attached to. 139 | 140 | We can annotate a Haskell definition by writing a comment block prefixed with `|` *before* 141 | the definition, or by writing a comment block prefixed with `^` *after* the definition. 142 | 143 | For example: 144 | 145 | ```hs 146 | -- | Construct an HTML page from a `Head` 147 | -- and a `Structure`. 148 | html_ 149 | :: Head -- ^ Represents the @\@ section in an HTML file 150 | -> Structure -- ^ Represents the @\@ section in an HTML file 151 | -> Html 152 | html_ = ... 153 | ... 154 | ``` 155 | 156 | Here's another example: 157 | 158 | ```hs 159 | {- | Represents a single markup structure. Such as: 160 | 161 | - A paragraph 162 | - An unordered list 163 | - A code block 164 | -} 165 | data Structure 166 | = Heading Natural String 167 | -- ^ A section heading with a level 168 | | Paragraph String 169 | -- ^ A paragraph 170 | | UnorderedList [String] 171 | -- ^ An unordered list of strings 172 | | OrderedList [String] 173 | -- ^ An ordered list of strings 174 | | CodeBlock [String] 175 | -- ^ A code block 176 | ``` 177 | 178 | And another: 179 | 180 | ```hs 181 | {- | Markup to HTML conversion module. 182 | 183 | This module handles converting documents written in our custom 184 | Markup language into HTML pages. 185 | -} 186 | module HsBlog.Convert where 187 | ``` 188 | 189 | As you can see, `|` and `^` can be used to document functions, function arguments, 190 | types, data constructors, modules, and more. They are probably the most important 191 | Haddock annotations to remember (and even then, `|` alone will suffice). 192 | 193 | > **Tip**: Annotate the modules, types, and the top-level definitions 194 | > which are exported from your project 195 | > with some high-level description of what they are used for (at the very least). 196 | > 197 | > Your users and collaborators will thank you! 198 | 199 | ### Section headings 200 | 201 | We can separate our module into sections by adding headings. 202 | Headings are comments prefixed with a number of `*` (just like in our markup language). 203 | 204 | For example: 205 | 206 | ```hs 207 | -- * HTML EDSL 208 | 209 | html_ :: Head -> Structure -> Html 210 | html_ = ... 211 | 212 | -- ** Structure 213 | 214 | p_ :: Content -> Structure 215 | p_ = .. 216 | 217 | h_ :: Content -> Structure 218 | h_ = .. 219 | 220 | ... 221 | 222 | -- ** Content 223 | 224 | txt_ :: String -> Content 225 | txt_ = ... 226 | 227 | link_ :: FilePath -> Content -> Content 228 | link_ = ... 229 | ``` 230 | 231 | It is also possible to add headings to the export list instead: 232 | 233 | ```hs 234 | module HsBlog.Html 235 | ( -- * HTML EDSL 236 | Html 237 | , html_ 238 | 239 | -- ** Combinators used to construct the @\@ section 240 | , Head 241 | , title_ 242 | , stylesheet_ 243 | , meta_ 244 | 245 | -- ** Combinators used to construct the @\@ section 246 | , Structure 247 | , p_ 248 | , h_ 249 | , ul_ 250 | , ol_ 251 | , code_ 252 | 253 | -- ** Combinators used to construct content inside structures 254 | , Content 255 | , txt_ 256 | , img_ 257 | , link_ 258 | , b_ 259 | , i_ 260 | 261 | -- ** Render HTML to String 262 | , render 263 | ) 264 | where 265 | ``` 266 | 267 | Separating parts of the module into sections helps keep the important things together 268 | and Haddock will create a table of contents at the top of a module page for us as well. 269 | 270 | Sometimes it's also easier to figure out whether a module should be split into multiple 271 | modules or not after splitting it into sections using headings. 272 | 273 | --- 274 | 275 | **Exercise**: Try to re-arrange the modules in our project to your liking and add headings to sections. 276 | 277 | --- 278 | 279 | ### Formatting 280 | 281 | As we saw earlier, we can also add formatting in the content of our comments. 282 | For example, we can: 283 | 284 | - Hyperlink identifiers by surrounding them with `` ` `` 285 | 286 | For example: `` `Heading` `` 287 | - Write `monospaced text` by surrounding it with `@` 288 | 289 | For example: `@Paragraph "Hello"@` 290 | - Add _emphasis_ to text by surrounding it with `/` 291 | 292 | For example: `/this is emphasised/` 293 | - Add __bold__ to text by surrounding it with `__` 294 | 295 | For example: `__this is bold__` 296 | 297 | ### More 298 | 299 | In this chapter, we've covered the basics of the Haddock markup language. 300 | If you'd like to know more, the [Haddock markup guide](https://haskell-haddock.readthedocs.io/en/latest/markup.html) 301 | contains information on creating even more interesting documentation structures, such as 302 | code blocks, grid tables, images, and examples. 303 | 304 | ## Summary 305 | 306 | We've briefly covered one aspect of documenting Haskell programs: 307 | using Haddock to generate informative API reference pages created from source code 308 | comments which are annotated with Haddock markup. 309 | 310 | While API references are incredibly valuable, remember that there are other forms of 311 | documentation that can help your users get started quickly, such as examples and tutorials. 312 | 313 | 314 | --- 315 | 316 | **Exercise**: Add haddock annotation to the top-level definitions in our project and test your understanding 317 | of the program and the various parts - sometimes, the best way to learn something is to try explaining it! 318 | 319 | --- 320 | -------------------------------------------------------------------------------- /src/10-recap.md: -------------------------------------------------------------------------------- 1 | # Recap 2 | 3 | In this book, we've implemented a very simple static blog generator while learning Haskell as we go. 4 | 5 | - We've learned about basic Haskell building blocks, such as definitions, functions, 6 | types, modules, recursion, pattern matching, type classes, IO, and exceptions. 7 | - We've learned about [EDSLs](./03-html/03-edsls.html) and used the *combinator pattern* to implement 8 | a composable html generation library. 9 | - We've learned how to leverage types, modules, and smart constructors 10 | to [make invalid states unrepresentable](./03-html/04-safer_construction.html). 11 | - We've learned how to represent complex data using [ADTs](./04-markup/01-data_type.html). 12 | - We've learned how to use [pattern matching](./04-markup/04-parsing_02.html#pattern-matching) to transform ADTs, 13 | and how to use [recursion](./04-markup/02-parsing_01.html#recursion-and-accumulating-information) to solve problems. 14 | - We've used the *functional core, imperative shell* approach to build a program that handles IO and applies 15 | our domain logic to user inputs. 16 | - We've learned about abstractions such as [monoids](./05-glue/01-markup_to_html.html#monoids), 17 | [functors](./05-glue/04-optparse.html#functor) and [monads](./06-errors_and_files/01-either.html#monadic-interface), 18 | and how they can help us reuse code and convey information about shared interfaces. 19 | - We've learned how to create fancy [command-line interfaces](./05-glue/04-optparse.html), [write tests](./08-testing.html), 20 | and [generate documentation](./09-documentation.html). 21 | 22 | While Haskell is a very big and complex language, and there's always more to be learned, 23 | I think we've reached an important milestone where 24 | you can start building your own Haskell projects and be productive with Haskell! 25 | 26 | This is a good time to celebrate and pat yourself on the back for getting this far! Great job, you! 27 | 28 | If you'd like to learn even more about Haskell and continue your Haskell journey 29 | beyond this book, check out the appendix sections [Where to go next](./11-next.md) and the [FAQ](./12-faq.md). 30 | 31 | ## Thank you! 32 | 33 | Thank you for reading this book. I hope you enjoyed it and found Haskell interesting. 34 | 35 | I would very much like to hear your feedback. If you'd like, you could leave your 36 | feedback on this book's 37 | [discussion board](https://github.com/soupi/learn-haskell-blog-generator/discussions), 38 | or you could reach me via email. 39 | You can find my contact information [on my website](https://gilmi.me). 40 | 41 | If you liked this book, do let me know - your kind words mean a lot. 42 | 43 | > Finally, if you *really* liked this book and would like to support future passion projects 44 | > like it, you can [support me directly via Ko-fi](https://ko-fi.com/gilmi). 45 | 46 | Thank you, and good luck with your next Haskell project! 47 | -------------------------------------------------------------------------------- /src/11-next.md: -------------------------------------------------------------------------------- 1 | # Where to go next 2 | 3 | Haskell is an incredibly rich and deep programming language. 4 | New cutting-edge techniques, concepts, and features are still being discovered 5 | and sometimes integrated into GHC. This sometimes makes it seemingly impossible 6 | to catch up to. 7 | 8 | This phenomena is sometimes dubbed 9 | [The Haskell pyramid](https://patrickmn.com/software/the-haskell-pyramid/). 10 | I hope that by reading this book and following the exercises, 11 | you readers have reached the bar of productivity, and you can now go and start 12 | working on your own projects with Haskell. I highly encourage you to do so. 13 | In my opinion, writing useful Haskell projects is the best method to solidify 14 | what you currently know and identify what you still need to learn. 15 | 16 | ## Extending this project 17 | 18 | If you'd like to extend this project, here are a few ideas for you: 19 | 20 | 1. **Serve over HTTP** - You can use a web library such as 21 | [wai](https://www.youtube.com/watch?v=mz5_HmLGRXc) or 22 | [twain](https://gilmi.me/blog/post/2022/04/24/learn-twain-bulletin-app) 23 | to serve this blog over HTTP instead of generating it statically 24 | 2. **Rewrite it with libraries** - you could rewrite it and use a real-world 25 | [HTML package](https://hackage.haskell.org/package/lucid) 26 | and [markdown parser](https://hackage.haskell.org/package/cmark-gfm) 27 | 3. **Add features** 28 | 1. You could add a metadata block at the top of each article 29 | which would include the title, publish date, and tags of a blog post, 30 | and use them when generating HTML, index page, and even tags pages 31 | 2. You could add HTML pages templating using 32 | [mustache](https://hackage.haskell.org/package/mustache) or similar, 33 | and use that to generate a nice and customizable structure to the page 34 | 3. You could add support for links and images in our markup language parser 35 | 4. You could add support for configuration files which would include things like 36 | the blog title, description, or other meta information for things like 37 | [twitter cards](https://developer.twitter.com/en/docs/twitter-for-websites/cards/overview/abouts-cards) 38 | 39 | Or anything else you can think of, consider this project your playground and 40 | do whatever you like with it! 41 | 42 | ## Other resources 43 | 44 | At some point, you are likely to run into new concepts, techniques, 45 | or even just a feeling of "I feel like this could be done better". 46 | I'd like to point you in the right direction so you can find additional information 47 | and learn new Haskell things when you need to or want to. 48 | 49 | I've compiled a list of resources for learning Haskell called 50 | [Haskell study plan](https://github.com/soupi/haskell-study-plan), 51 | which includes links to very useful articles, community hubs, and news aggregators, 52 | project suggestions, and even cool open-source Haskell projects. 53 | You will also find alternative explanations for things we've covered 54 | and even links to other Haskell tutorials, guides, and books in case you need 55 | a different view on things. 56 | 57 | Also, the [GHC User Guide](https://downloads.haskell.org/ghc/latest/docs/users_guide/index.html) 58 | is a fantastic resource with loads of articles and information about the language and GHC tooling around it. 59 | It is often the best place to learn about the Haskell language. 60 | 61 | However, don't feel pressured to learn everything Haskell 62 | has to offer right away. Mastering Haskell is a journey that can take a lot of time. 63 | Most of us are definitely not there yet, but we can still be very productive with Haskell, 64 | build real-world projects, and even discover new techniques and concepts ourselves. 65 | 66 | Remember that in a lazy language, we evaluate things only when needed. 67 | Maybe we can do that too, with Haskell concepts! 68 | -------------------------------------------------------------------------------- /src/12-faq.md: -------------------------------------------------------------------------------- 1 | # Frequently asked questions 2 | 3 | > Got a question? You can ask in the [discussion board](https://github.com/soupi/learn-haskell-blog-generator/discussions) or the [issue tracker](https://github.com/soupi/learn-haskell-blog-generator/issues)! 4 | 5 | ## General questions 6 | 7 | ### Why should I learn Haskell 8 | 9 | I've written a couple of articles on the topic: 10 | 11 | - [Consider Haskell](https://gilmi.me/blog/post/2020/04/28/consider-haskell) (Alternative title, 'What can I do with Haskell?') 12 | - [7 things I learned from Haskell](https://gilmi.me/blog/post/2022/12/13/learned-from-haskell) 13 | 14 | ### How to install editor tools 15 | 16 | As far as I know, the most recommended setup today for Haskell development is using 17 | VSCode or [VSCodium](https://vscodium.com/) together with the 18 | marketplace [Haskell extension](https://marketplace.visualstudio.com/items?itemName=haskell.haskell). 19 | 20 | The Haskell extension uses [haskell-language-server](https://github.com/haskell/haskell-language-server) 21 | which can be installed via [GHCup](https://www.haskell.org/ghcup/) or even via the Haskell extension itself. 22 | 23 | If you already have a preferred editor, 24 | [see if HLS supports it](https://haskell-language-server.readthedocs.io/en/latest/configuration.html#configuring-your-editor), 25 | or alternatively use [GHCid](https://github.com/ndmitchell/ghcid#readme) 26 | which provides rapid feedback independently from an editor. 27 | 28 | ### How to learn new things 29 | 30 | The Haskell community keeps marching forward, developing new libraries, tools, and techniques 31 | as well as creating new material for older concepts. 32 | The [Haskell planetarium](https://haskell.pl-a.net) aggregates feeds from several communities into 33 | one page, as well as a [Haskell Weekly newsletter](https://haskellweekly.news/). 34 | You might also find quite a bit of Haskell presence on the 35 | [Fediverse](https://fosstodon.org/tags/haskell)! 36 | 37 | ## Debugging 38 | 39 | ### How to debug Haskell code 40 | 41 | Most imperative languages provide a step debugger. While the 42 | [GHCi debugger](https://downloads.haskell.org/ghc/latest/docs/users_guide/ghci.html#the-ghci-debugger), 43 | exists, it is not particularly easy to use, especially because of Haskell's lazy evaluation, where things 44 | might not be evaluated in the order we might intuitively expect. Because of that, 45 | Haskellers tend to use 46 | [trace debugging](https://hackage.haskell.org/package/base-4.16.4.0/docs/Debug-Trace.html#g:1) and 47 | equational reasoning. With trace debugging, we try to *verify our assumptions* about the code - 48 | we use the various `trace` functions as a "hack" to print variables, functions inputs, functions output 49 | or even just say "got here" from anywhere in the code. 50 | 51 | After finding something that does not match our assumptions, such as unexpected input or output 52 | of a function, we try to think what piece of code could be responsible for the discrepancy or even use 53 | trace debugging again to pinpoint the exact location, and try to use "equational reasoning" to 54 | evaluate the offending code that betrayed our expectations. If it's easy to do, we try running 55 | the function in `ghci` with different inputs to check our assumptions as well. 56 | 57 | Because Haskell focuses on immutability, composability, and using types to eliminate many 58 | classes of possible errors, "local reasoning" becomes possible, and trace debugging 59 | becomes a viable strategy for debugging Haskell programs. 60 | 61 | ### How to understand type errors 62 | 63 | GHC type errors are often not the most friendly error messages, but they mean well! They are just 64 | trying to help us find inconsistencies in our code - often with regard to type usage, they help us 65 | avoid making errors. 66 | 67 | When you run into error messages, start by reading them carefully 68 | until you get used to them, and then the offending code hinted at by the error message. 69 | As you gain experience, it is likely that the most important part of an error will be the location 70 | of the offending code, and by reading the code, we can find the error without the actual error message. 71 | 72 | Adding type signatures and annotations to test your understanding of the types also helps greatly. 73 | We can even ask GHC for the expected type in a certain place by using 74 | [typed holes](https://downloads.haskell.org/ghc/latest/docs/users_guide/exts/typed_holes.html). 75 | 76 | ### My program is slow. Why? 77 | 78 | There could be various reasons. From inefficient algorithms or 79 | [unsuited data structures](https://github.com/soupi/haskell-study-plan#data-structures) for the task 80 | in terms of time complexity of the common operations, to less efficient memory representations 81 | (this is another reminder to use `Text` over `String` in most cases), 82 | and laziness issues (again, the evaluation strategy!). 83 | 84 | The [performance section](https://github.com/soupi/haskell-study-plan#performance) in my Haskell 85 | study plan links to various resources on Haskell evaluation, profiling, and case studies. 86 | 87 | ## Design 88 | 89 | ### How to structure programs 90 | 91 | Start with the imperative shell functional core approach, define EDSLs with the combinator 92 | pattern for logic if needed, use capabilities such as `State` locally if needed, 93 | maybe add an environment configuration with `ReaderT`, and see how it goes. 94 | 95 | If that approach fails you, look at why it fails and examine other solutions according to your needs. 96 | 97 | ### How to model data 98 | 99 | Modeling data using ADTs are usually the way to go. Often programmers coming from object-oriented 100 | background tend to look at type classes as a way to define methods similar to inheritance, 101 | but this often isn't the right approach, and ADTs with different constructors for different alternatives 102 | go a long way. Remember that even OOP people often preach for composition over inheritance. 103 | 104 | Use functions to define behavior on data rather than trying to couple the two together. 105 | -------------------------------------------------------------------------------- /src/SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | - [About this book](./01-about.md) 4 | - [Hello world](./02-hello.md) 5 | - [Building an HTML printer library](./03-html_printer.md) 6 | - [Flexible HTML content (functions)](./03-html/01-html_content.md) 7 | - [Adding type signatures](./03-html/02-type_signatures.md) 8 | - [Embedded Domain Specific Languages](./03-html/03-edsls.md) 9 | - [Safer HTML construction with types](./03-html/04-safer_construction.md) 10 | - [Preventing incorrect use with modules](./03-html/05-modules.md) 11 | - [Escaping characters](./03-html/06-escaping_characters.md) 12 | - [Exposing internal functionality (Internal modules)](./03-html/07-internal_modules.md) 13 | - [Exercises](./03-html/08-exercises.md) 14 | - [Summary](./03-html/09-summary.md) 15 | - [Custom markup language](./04-markup.md) 16 | - [Representing the markup language as a Haskell data type](./04-markup/01-data_type.md) 17 | - [Parsing markup part 01 (Recursion)](./04-markup/02-parsing_01.md) 18 | - [Displaying the parsing results (type classes)](./04-markup/03-displaying_results.md) 19 | - [Parsing markup part 02 (Pattern matching)](./04-markup/04-parsing_02.md) 20 | - [Gluing things together](./05-glue.md) 21 | - [Converting Markup to HTML](./05-glue/01-markup_to_html.md) 22 | - [Working with IO](./05-glue/02-io.md) 23 | - [Defining a project description](./05-glue/03-project.md) 24 | - [Fancy options parsing](./05-glue/04-optparse.md) 25 | - [Handling errors and multiple files](./06-errors_and_files.md) 26 | - [Handling errors with Either](./06-errors_and_files/01-either.md) 27 | - [Either with IO?](./06-errors_and_files/02-except.md) 28 | - [Exceptions](./06-errors_and_files/03-exceptions.md) 29 | - [Let's code already!](./06-errors_and_files/04-implementation.md) 30 | - [Summary](./06-errors_and_files/05-summary.md) 31 | - [Passing an environment](./07-environment.md) 32 | - [Writing tests](./08-testing.md) 33 | - [Generating documentation](./09-documentation.md) 34 | 35 | - [Recap](./10-recap.md) 36 | 37 | --- 38 | 39 | [Where to go next](./11-next.md) 40 | 41 | [Frequently asked questions](./12-faq.md) 42 | --------------------------------------------------------------------------------