Skip to content

Commit 1d54f88

Browse files
authored
Introduce markdownlint (#423)
* Introduce markdownlint * Fix: disable commands-show-output of markdownlint This is to ensure consistency with respect to existing notation. text/chapter1.md: > Commands which should be typed at the command line will be preceded by a dollar symbol: > > ```text > $ spago build > ```
1 parent d6e0954 commit 1d54f88

16 files changed

+104
-76
lines changed

.github/workflows/tests.yml

+4-1
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ jobs:
4141
${{ runner.os }}-
4242
4343
- name: Install NPM dependencies
44-
run: npm install purescript spago
44+
run: npm install purescript spago markdownlint-cli
4545

4646
- name: Run Build
4747
run: ./scripts/buildAll.sh
@@ -60,3 +60,6 @@ jobs:
6060

6161
- name: Run tests again
6262
run: ./scripts/testAll.sh
63+
64+
- name: Run Markdown lint
65+
run: npx markdownlint **/*.md

.markdownlint.json

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"line-length": false,
3+
"no-duplicate-heading": false,
4+
"no-trailing-punctuation": false,
5+
"commands-show-output": false
6+
}

CONTRIBUTING.md

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
1+
# Contributing
2+
13
Please open PRs or Issues if you find anything to improve in the book.
24

35
We appreciate your feedback.
46

5-
### Editing chapter text:
7+
## Editing chapter text
68

79
To test changes to the text locally, install [mdbook](https://github.com/rust-lang/mdBook), then run this command from repo root:
810

911
```sh
1012
mdbook serve -o
1113
```
14+
1215
The rendered webpage will automatically refresh with any changes to readme files.

text/SUMMARY.md

+1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# Summary
22

33
[Foreword](../README.md)
4+
45
* [Introduction](chapter1.md)
56
* [Getting Started](chapter2.md)
67
* [Functions and Records](chapter3.md)

text/chapter10.md

+14-16
Original file line numberDiff line numberDiff line change
@@ -106,21 +106,18 @@ $ spago repl
106106

107107
Let's rewrite our `diagonal` function from Chapter 2 in a foreign module. This function calculates the diagonal of a right-angled triangle.
108108

109-
110109
```hs
111110
{{#include ../exercises/chapter10/test/Examples.purs:diagonal}}
112111
```
113112

114113
Recall that functions in PureScript are _curried_. `diagonal` is a function that takes a `Number` and returns a _function_, that takes a `Number` and returns a `Number`.
115114

116-
117115
```js
118116
{{#include ../exercises/chapter10/test/Examples.js:diagonal}}
119117
```
120118

121119
Or with ES6 arrow syntax (see ES6 note below).
122120

123-
124121
```js
125122
{{#include ../exercises/chapter10/test/Examples.js:diagonal_arrow}}
126123
```
@@ -403,7 +400,7 @@ which returns `Just 1000` for any array input.
403400
This vulnerability is allowed because `(\_ -> Just 1000)` and `Just 1000` match the signatures of `(a -> Maybe a)` and `Maybe a` respectively when `a` is `Int` (based on input array).
404401

405402
In the more secure type signature, even when `a` is determined to be `Int` based on the input array, we still need to provide valid functions matching the signatures involving `forall x`.
406-
The *only* option for `(forall x. Maybe x)` is `Nothing`, since a `Just` value would assume a type for `x` and will no longer be valid for all `x`. The only options for `(forall x. x -> Maybe x)` are `Just` (our desired argument) and `(\_ -> Nothing)`, which is the only remaining vulnerability.
403+
The _only_ option for `(forall x. Maybe x)` is `Nothing`, since a `Just` value would assume a type for `x` and will no longer be valid for all `x`. The only options for `(forall x. x -> Maybe x)` are `Just` (our desired argument) and `(\_ -> Nothing)`, which is the only remaining vulnerability.
407404

408405
## Defining Foreign Types
409406

@@ -488,7 +485,7 @@ export const unsafeHead = arr => {
488485
}
489486
```
490487

491-
Write a JavaScript function `quadraticRootsImpl` and a wrapper `quadraticRoots :: Quadratic -> Pair Complex` that uses the quadratic formula to find the roots of this polynomial. Return the two roots as a `Pair` of `Complex` numbers. *Hint:* Use the `quadraticRoots` wrapper to pass a constructor for `Pair` to `quadraticRootsImpl`.
488+
Write a JavaScript function `quadraticRootsImpl` and a wrapper `quadraticRoots :: Quadratic -> Pair Complex` that uses the quadratic formula to find the roots of this polynomial. Return the two roots as a `Pair` of `Complex` numbers. _Hint:_ Use the `quadraticRoots` wrapper to pass a constructor for `Pair` to `quadraticRootsImpl`.
492489

493490
1. (Medium) Write the function `toMaybe :: forall a. Undefined a -> Maybe a`. This function converts `undefined` to `Nothing` and `a` values to `Just`s.
494491

@@ -675,7 +672,7 @@ unit
675672
done waiting
676673
```
677674

678-
Note that asynchronous logging in the repl just waits to print until the entire block has finished executing. This code behaves more predictably when run with `spago test` where there is a slight delay *between* prints.
675+
Note that asynchronous logging in the repl just waits to print until the entire block has finished executing. This code behaves more predictably when run with `spago test` where there is a slight delay _between_ prints.
679676

680677
Let's look at another example where we return a value from a promise. This function is written with `async` and `await`, which is just syntactic sugar for promises.
681678

@@ -715,6 +712,7 @@ unit
715712
```
716713

717714
## Exercises
715+
718716
Exercises for the above sections are still on the ToDo list. If you have any ideas for good exercises, please make a suggestion.
719717

720718
## JSON
@@ -1073,7 +1071,7 @@ Left errs -> alert $ "There are " <> show (length errs) <> " validation errors."
10731071
alert $ "Error: " <> err <> ". Loading examplePerson"
10741072
```
10751073

1076-
## Exercises
1074+
## Exercises
10771075

10781076
1. (Easy) Write a wrapper for the `removeItem` method on the `localStorage` object, and add your foreign function to the `Effect.Storage` module.
10791077
1. (Medium) Add a "Reset" button that, when clicked, calls the newly-created `removeItem` function to delete the "person" entry from local storage.
@@ -1090,9 +1088,9 @@ In this chapter, we've learned how to work with foreign JavaScript code from Pur
10901088

10911089
For more examples, the `purescript`, `purescript-contrib` and `purescript-node` GitHub organizations provide plenty of examples of libraries which use the FFI. In the remaining chapters, we will see some of these libraries put to use to solve real-world problems in a type-safe way.
10921090

1093-
# Addendum
1091+
## Addendum
10941092

1095-
## Calling PureScript from JavaScript
1093+
### Calling PureScript from JavaScript
10961094

10971095
Calling a PureScript function from JavaScript is very simple, at least for functions with simple types.
10981096

@@ -1127,7 +1125,7 @@ var Test = PS.Test;
11271125
Test.gcd(15)(20);
11281126
```
11291127

1130-
## Understanding Name Generation
1128+
### Understanding Name Generation
11311129

11321130
PureScript aims to preserve names during code generation as much as possible. In particular, most identifiers which are neither PureScript nor JavaScript keywords can be expected to be preserved, at least for names of top-level declarations.
11331131

@@ -1157,7 +1155,7 @@ var example$prime = 100;
11571155

11581156
Where compiled PureScript code is intended to be called from JavaScript, it is recommended that identifiers only use alphanumeric characters, and avoid JavaScript keywords. If user-defined operators are provided for use in PureScript code, it is good practice to provide an alternative function with an alphanumeric name for use in JavaScript.
11591157

1160-
## Runtime Data Representation
1158+
### Runtime Data Representation
11611159

11621160
Types allow us to reason at compile-time that our programs are "correct" in some sense - that is, they will not break at runtime. But what does that mean? In PureScript, it means that the type of an expression should be compatible with its representation at runtime.
11631161

@@ -1179,7 +1177,7 @@ As you might expect, PureScript's arrays correspond to JavaScript arrays. But re
11791177

11801178
We've already seen that PureScript's records evaluate to JavaScript objects. Just as for functions and arrays, we can reason about the runtime representation of data in a record's fields by considering the types associated with its labels. Of course, the fields of a record are not required to be of the same type.
11811179

1182-
## Representing ADTs
1180+
### Representing ADTs
11831181

11841182
For every constructor of an algebraic data type, the PureScript compiler creates a new JavaScript object type by defining a function. Its constructors correspond to functions which create new JavaScript objects based on those prototypes.
11851183

@@ -1243,7 +1241,7 @@ newtype PhoneNumber = PhoneNumber String
12431241

12441242
is actually represented as a JavaScript string at runtime. This is useful for designing libraries, since newtypes provide an additional layer of type safety, but without the runtime overhead of another function call.
12451243

1246-
## Representing Quantified Types
1244+
### Representing Quantified Types
12471245

12481246
Expressions with quantified (polymorphic) types have restrictive representations at runtime. In practice, this means that there are relatively few expressions with a given quantified type, but that we can reason about them quite effectively.
12491247

@@ -1282,7 +1280,7 @@ Without being able to inspect the runtime type of our function argument, our onl
12821280

12831281
A full discussion of _parametric polymorphism_ and _parametricity_ is beyond the scope of this book. Note however, that since PureScript's types are _erased_ at runtime, a polymorphic function in PureScript _cannot_ inspect the runtime representation of its arguments (without using the FFI), and so this representation of polymorphic data is appropriate.
12841282

1285-
## Representing Constrained Types
1283+
### Representing Constrained Types
12861284

12871285
Functions with a type class constraint have an interesting representation at runtime. Because the behavior of the function might depend on the type class instance chosen by the compiler, the function is given an additional argument, called a _type class dictionary_, which contains the implementation of the type class functions provided by the chosen instance.
12881286

@@ -1313,7 +1311,7 @@ import { showNumber } from 'Data.Show'
13131311
shout(showNumber)(42);
13141312
```
13151313

1316-
## Exercises
1314+
### Exercises
13171315

13181316
1. (Easy) What are the runtime representations of these types?
13191317

@@ -1326,7 +1324,7 @@ shout(showNumber)(42);
13261324
What can you say about the expressions which have these types?
13271325
1. (Medium) Try using the functions defined in the `arrays` package, calling them from JavaScript, by compiling the library using `spago build` and importing modules using the `import` function in NodeJS. _Hint_: you may need to configure the output path so that the generated ES modules are available on the NodeJS module path.
13281326

1329-
## Representing Side Effects
1327+
### Representing Side Effects
13301328

13311329
The `Effect` monad is also defined as a foreign type. Its runtime representation is quite simple - an expression of type `Effect a` should evaluate to a JavaScript function of **no arguments**, which performs any side-effects and returns a value with the correct runtime representation for type `a`.
13321330

text/chapter11.md

+11-10
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ For example, to provide the player name using the `-p` option:
3939
$ spago run -a "-p Phil"
4040
>
4141
```
42+
4243
```text
4344
$ spago bundle-app
4445
$ node index.js -p Phil
@@ -136,7 +137,7 @@ Given the `sumArray` function above, we could use `execState` in PSCi to sum the
136137
21
137138
```
138139

139-
## Exercises
140+
## Exercises
140141

141142
1. (Easy) What is the result of replacing `execState` with `runState` or `evalState` in our example above?
142143
1. (Medium) A string of parentheses is _balanced_ if it is obtained by either concatenating zero-or-more shorter balanced
@@ -220,7 +221,7 @@ To run a computation in the `Reader` monad, the `runReader` function can be used
220221
runReader :: forall r a. Reader r a -> r -> a
221222
```
222223

223-
## Exercises
224+
## Exercises
224225

225226
In these exercises, we will use the `Reader` monad to build a small library for rendering documents with indentation. The "global configuration" will be a number indicating the current indentation level:
226227

@@ -339,7 +340,7 @@ We can test our modified function in PSCi:
339340
Tuple 3 ["gcdLog 21 15","gcdLog 6 15","gcdLog 6 9","gcdLog 6 3","gcdLog 3 3"]
340341
```
341342

342-
## Exercises
343+
## Exercises
343344

344345
1. (Medium) Rewrite the `sumArray` function above using the `Writer` monad and the `Additive Int` monoid from the `monoid` package.
345346
1. (Medium) The _Collatz_ function is defined on natural numbers `n` as `n / 2` when `n` is even, and `3 * n + 1` when `n` is odd. For example, the iterated Collatz sequence starting at `10` is as follows:
@@ -545,7 +546,7 @@ One problem with this code is that we have to use the `lift` function multiple t
545546

546547
Fortunately, as we will see, we can use the automatic code generation provided by type class inference to do most of this "heavy lifting" for us.
547548

548-
## Exercises
549+
## Exercises
549550

550551
1. (Easy) Use the `ExceptT` monad transformer over the `Identity` functor to write a function `safeDivide` which divides two numbers, throwing an error (as the String "Divide by zero!") if the denominator is zero.
551552
1. (Medium) Write a parser
@@ -712,7 +713,7 @@ We can even use `many` to fully split a string into its lower and upper case com
712713

713714
Again, this illustrates the power of reusability that monad transformers bring - we were able to write a backtracking parser in a declarative style with only a few lines of code, by reusing standard abstractions!
714715

715-
## Exercises
716+
## Exercises
716717

717718
1. (Easy) Remove the calls to the `lift` function from your implementation of the `string` parser. Verify that the new implementation type checks, and convince yourself that it should.
718719
1. (Medium) Use your `string` parser with the `some` combinator to write a parser `asFollowedByBs` which recognizes strings consisting of several copies of the string `"a"` followed by several copies of the string `"b"`.
@@ -911,7 +912,7 @@ The `runGame` function finally attaches the initial line handler to the console
911912
{{#include ../exercises/chapter11/src/Main.purs:runGame_attach_handler}}
912913
```
913914

914-
## Exercises
915+
## Exercises
915916

916917
1. (Medium) Implement a new command `cheat`, which moves all game items from the game grid into the user's inventory. Create a function `cheat :: Game Unit` in the `Game` module, and use this function from `game`.
917918
1. (Difficult) The `Writer` component of the `RWS` monad is currently used for two types of messages: error messages and informational messages. Because of this, several parts of the code use case statements to handle error cases.
@@ -937,13 +938,14 @@ This is best illustrated by example. The application's `main` function is define
937938
The first argument is used to configure the `optparse` library. In our case, we simply configure it to show the help message when the application is run without any arguments (instead of showing a "missing argument" error) by using `OP.prefs OP.showHelpOnEmpty`, but the `Options.Applicative.Builder` module provides several other options.
938939

939940
The second argument is the complete description of our parser program:
940-
```haskell
941+
942+
```haskell
941943
{{#include ../exercises/chapter11/src/Main.purs:argParser}}
942944

943945
{{#include ../exercises/chapter11/src/Main.purs:parserOptions}}
944946
```
945947

946-
Here `OP.info` combines a `Parser` with a set of options for how the help message is formatted. `env <**> OP.helper` takes any command line argument `Parser` named `env` and adds a `--help` option to it automatically. Options for the help message are of type `InfoMod`, which is a monoid, so we can use the `fold` function to add several options together.
948+
Here `OP.info` combines a `Parser` with a set of options for how the help message is formatted. `env <**> OP.helper` takes any command line argument `Parser` named `env` and adds a `--help` option to it automatically. Options for the help message are of type `InfoMod`, which is a monoid, so we can use the `fold` function to add several options together.
947949

948950
The interesting part of our parser is constructing the `GameEnvironment`:
949951

@@ -955,7 +957,7 @@ The interesting part of our parser is constructing the `GameEnvironment`:
955957

956958
Notice how we were able to use the notation afforded by the applicative operators to give a compact, declarative specification of our command line interface. In addition, it is simple to add new command line arguments, simply by adding a new function argument to `runGame`, and then using `<*>` to lift `runGame` over an additional argument in the definition of `env`.
957959

958-
## Exercises
960+
## Exercises
959961

960962
1. (Medium) Add a new Boolean-valued property `cheatMode` to the `GameEnvironment` record. Add a new command line flag `-c` to the `optparse` configuration which enables cheat mode. The `cheat` command from the previous exercise should be disallowed if cheat mode is not enabled.
961963

@@ -968,4 +970,3 @@ Because we separated our implementation from the user interface, it would be pos
968970
We have seen how monad transformers allow us to write safe code in an imperative style, where effects are tracked by the type system. In addition, type classes provide a powerful way to abstract over the actions provided by a monad, enabling code reuse. We were able to use standard abstractions like `Alternative` and `MonadPlus` to build useful monads by combining standard monad transformers.
969971

970972
Monad transformers are an excellent demonstration of the sort of expressive code that can be written by relying on advanced type system features such as higher-kinded polymorphism and multi-parameter type classes.
971-

text/chapter12.md

+3-3
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ $ spago bundle-app --main Example.Shapes --to dist/Main.js
150150

151151
and open `html/index.html` again to see the result. You should see the three different types of shapes rendered to the canvas.
152152

153-
## Exercises
153+
## Exercises
154154

155155
1. (Easy) Experiment with the `strokePath` and `setStrokeStyle` functions in each of the examples so far.
156156
1. (Easy) The `fillPath` and `strokePath` functions can be used to render complex paths with a common style by using a do notation block inside the function argument. Try changing the `Rectangle` example to render two rectangles side-by-side using the same call to `fillPath`. Try rendering a sector of a circle by using a combination of a piecewise-linear path and an arc segment.
@@ -358,7 +358,7 @@ $ spago bundle-app --main Example.Refs --to dist/Main.js
358358

359359
and open the `html/index.html` file. If you click the canvas repeatedly, you should see a green rectangle rotating around the center of the canvas.
360360

361-
## Exercises
361+
## Exercises
362362

363363
1. (Easy) Write a higher-order function which strokes and fills a path simultaneously. Rewrite the `Random.purs` example using your function.
364364
1. (Medium) Use `Random` and `Dom` to create an application which renders a circle with random position, color and radius to the canvas when the mouse is clicked.
@@ -552,7 +552,7 @@ $ spago bundle-app --main Example.LSystem --to dist/Main.js
552552

553553
and open `html/index.html`. You should see the Koch curve rendered to the canvas.
554554

555-
## Exercises
555+
## Exercises
556556

557557
1. (Easy) Modify the L-system example above to use `fillPath` instead of `strokePath`. _Hint_: you will need to include a call to `closePath`, and move the call to `moveTo` outside of the `interpret` function.
558558
1. (Easy) Try changing the various numerical constants in the code, to understand their effect on the rendered system.

0 commit comments

Comments
 (0)