`
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 | - Defines the main function in the source file
87 | - Defines the module name to be Main, or does not have a module declaration
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 | Hint
We 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 |
--------------------------------------------------------------------------------