Skip to content

Commit

Permalink
Merge branch 'master' into ja
Browse files Browse the repository at this point in the history
  • Loading branch information
gemmaro committed Aug 25, 2023
2 parents ac05dde + e418ab4 commit 93f2c77
Show file tree
Hide file tree
Showing 7 changed files with 143 additions and 74 deletions.
9 changes: 8 additions & 1 deletion scripts/removeAnchors.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,14 @@ set -x
set -e

# All .purs & .js files in the src/ and test/ directories of chapter exercises.
FILES=$(find . -regextype posix-extended -regex '\./exercises/chapter[0-9]{1,2}/(src|test)/.*\.(purs|js)' -type f)
FIND_FILES_PATTERN='\./exercises/chapter[0-9]{1,2}/(src|test)/.*\.(purs|js)'

EXTENDED_REGEX_FLAGS="-regextype posix-extended"
# BSD find has different flags for extended regex
if [[ $(uname) == 'FreeBSD' || $(uname) == 'Darwin' ]]; then
EXTENDED_REGEX_FLAGS='-E'
fi
FILES=$(find . -type f $EXTENDED_REGEX_FLAGS -regex $FIND_FILES_PATTERN)

for f in $FILES; do
# Delete lines starting with an 'ANCHOR' comment
Expand Down
4 changes: 3 additions & 1 deletion text/chapter1.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ Functions enable a simple form of abstraction that can yield great productivity

PureScript is a programming language that aims to address these issues. It features lightweight syntax, which allows us to write very expressive code which is still clear and readable. It uses a rich type system to support powerful abstractions. It also generates fast, understandable code, which is important when interoperating with JavaScript or other languages that compile to JavaScript. All in all, I hope to convince you that PureScript strikes a very practical balance between the theoretical power of purely functional programming and the fast-and-loose programming style of JavaScript.

> Note that PureScript can target other backends, not only JavaScript, but this book focuses on targeting web browser and node environments.

## Types and Type Inference

The debate over statically typed languages versus dynamically typed languages is well-documented. PureScript is a _statically typed_ language, meaning that a correct program can be given a _type_ by the compiler, which indicates its behavior. Conversely, programs that cannot be given a type are _incorrect programs_, and will be rejected by the compiler. In PureScript, unlike in dynamically typed languages, types exist only at _compile-time_ and have no representation at runtime.
Expand Down Expand Up @@ -144,7 +146,7 @@ If you get stuck at any point, there are a number of resources available online
- The [PureScript website](https://www.purescript.org) contains links to several learning resources, including code samples, videos, and other resources for beginners.
- [Try PureScript!](https://try.purescript.org) is a website that allows users to compile PureScript code in the web browser and contains several simple examples of code.

If you prefer to learn by reading examples, the `purescript`, `purescript-node`, and `purescript-contrib` GitHub organizations contain plenty of examples of PureScript code.
If you prefer to learn by reading examples, the [purescript](https://github.com/purescript), [purescript-node](https://github.com/purescript-node), and [purescript-contrib](https://github.com/purescript-contrib) GitHub organizations contain plenty of examples of PureScript code.

## About the Author

Expand Down
176 changes: 118 additions & 58 deletions text/chapter3.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,12 @@ The source code for this chapter is contained in the file `src/Data/AddressBook.

Here, we import several modules:

- The `Prelude` module, which contains a small set of standard definitions and functions. It re-exports many foundational modules from the `purescript-prelude` library.
- The `Control.Plus` module, which defines the `empty` value.
- The `Data.List` module, provided by the `lists` package, which can be installed using Spago. It contains a few functions that we will need for working with linked lists.
- The `Data.Maybe` module, which defines data types and functions for working with optional values.

Notice that the imports for these modules are listed explicitly in parentheses. This is generally a good practice, as it helps to avoid conflicting imports.
Notice that the imports for these modules are listed explicitly in parentheses (except for `Prelude`, which is typically imported as an open import). This is generally a good practice, as it helps to avoid conflicting imports.

Assuming you have cloned the book's source code repository, the project for this chapter can be built using Spago, with the following commands:

Expand Down Expand Up @@ -102,27 +103,19 @@ Fields of records can be accessed using a dot, followed by the label of the fiel
["Functional Programming","JavaScript"]
```

PureScript's functions correspond to JavaScript's functions. The PureScript standard libraries provide plenty of examples of functions, and we will see more in this chapter:

```text
> import Prelude
> :type flip
forall a b c. (a -> b -> c) -> b -> a -> c
> :type const
forall a b. a -> b -> a
```

Functions can be defined at the top-level of a file by specifying arguments before the equals sign:
PureScript's functions correspond to JavaScript's functions. Functions can be defined at the top-level of a file by specifying arguments before the equals sign:

```haskell
import Prelude -- bring the (+) operator into scope

add :: Int -> Int -> Int
add x y = x + y
```

Alternatively, functions can be defined inline using a backslash character followed by a space-delimited list of argument names. To enter a multi-line declaration in PSCi, we can enter "paste mode" using the `:paste` command. In this mode, declarations are terminated using the _Control-D_ key sequence:

```text
> import Prelude
> :paste
… add :: Int -> Int -> Int
… add = \x y -> x + y
Expand All @@ -136,44 +129,11 @@ Having defined this function in PSCi, we can _apply_ it to its arguments by sepa
30
```

## Quantified Types

In the previous section, we saw the types of some functions defined in the Prelude. For example, the `flip` function had the following type:

```text
> :type flip
forall a b c. (a -> b -> c) -> b -> a -> c
```

The keyword `forall` here indicates that `flip` has a _universally quantified type_. It means we can substitute any types for `a`, `b`, and `c`, and `flip` will work with those types.

For example, we might choose the type `a` to be `Int`, `b` to be `String`, and `c` to be `String`. In that case, we could _specialize_ the type of `flip` to

```text
(Int -> String -> String) -> String -> Int -> String
```

We don't have to indicate in code that we want to specialize a quantified type – it happens automatically. For example, we can use `flip` as if it had this type already:

```text
> flip (\n s -> show n <> s) "Ten" 10
"10Ten"
```

While we can choose any types for `a`, `b`, and `c`, we have to be consistent. The type of function passed to `flip` had to be consistent with the types of the other arguments. That is why we passed the string `"Ten"` as the second argument and the number `10` as the third. It would not work if the arguments were reversed:

```text
> flip (\n s -> show n <> s) 10 "Ten"
Could not match type Int with type String
```

## Notes On Indentation

PureScript code is _indentation-sensitive_, just like Haskell, but unlike JavaScript. This means that the whitespace in your code is not meaningless, but rather is used to group regions of code, just like curly braces in C-like languages.

If a declaration spans multiple lines, then any lines except the first must be indented past the indentation level of the first line.
If a declaration spans multiple lines, any lines except the first must be indented past the indentation level of the first line.

Therefore, the following is a valid PureScript code:

Expand Down Expand Up @@ -209,18 +169,29 @@ But this is not:
… ^D
```

Certain PureScript keywords (such as `where`, `of` and `let`) introduce a new block of code, in which declarations must be further-indented:
Certain PureScript keywords introduce a new block of code, in which declarations must be further-indented:

```haskell
example x y z = foo + bar
where
example x y z =
let
foo = x * y
bar = y * z
in
foo + bar
```

Note how the declarations for `foo` and `bar` are indented past the declaration of `example`.
This doesn't compile:

```haskell
example x y z =
let
foo = x * y
bar = y * z
in
foo + bar
```

The only exception to this rule is the `where` keyword in the initial `module` declaration at the top of a source file.
If you want to learn more (or encounter any problems), see the [Syntax](https://github.com/purescript/documentation/blob/master/language/Syntax.md#syntax) documentation.

## Defining Our Types

Expand Down Expand Up @@ -282,6 +253,52 @@ Type

PureScript's _kind system_ supports other interesting kinds, which we will see later in the book.

## Quantified Types

For illustration purposes, let's define a primitive function that takes any two arguments and returns the first one:

```text
> :paste
… constantlyFirst :: forall a b. a -> b -> a
… constantlyFirst = \a b -> a
… ^D
```

> Note that if you use `:type` to ask about the type of `constantlyFirst`, it will be more verbose:
>
> ```text
> : type constantlyFirst
> forall (a :: Type) (b :: Type). a -> b -> a
> ```
>
> The type signature contains additional kind information, which explicitly notes that `a` and `b` should be concrete types.
The keyword `forall` indicates that `constantlyFirst` has a _universally quantified type_. It means we can substitute any types for `a` and `b``constantlyFirst` will work with these types.

For example, we might choose the type `a` to be `Int` and `b``String`. In that case, we can _specialize_ the type of `constantlyFirst` to

```text
Int -> String -> Int
```

We don't have to indicate in code that we want to specialize a quantified type – it happens automatically. For example, we can use `constantlyFirst` as if it had this type already:

```text
> constantlyFirst 3 "ignored"
3
```

While we can choose any types for `a` and `b`, the return type of `constantlyFirst` has to be the same as the type of the first argument (because both of them are "tied" to the same `a`):

```text
:type constantlyFirst true "ignored"
Boolean
:type constantlyFirst "keep" 3
String
```

## Displaying Address Book Entries

Let's write our first function, which will render an address book entry as a string. We start by giving the function a type. This is optional, but good practice, since it acts as a form of documentation. In fact, the PureScript compiler will give a warning if a top-level declaration does not contain a type annotation. A type declaration separates the name of a function from its type with the `::` symbol:
Expand Down Expand Up @@ -371,7 +388,7 @@ $ spago repl
> import Data.List
> :type Cons
forall a. a -> List a -> List a
forall (a :: Type). a -> List a -> List a
```

This type signature says that `Cons` takes a value of some type `a`, takes a list of elements of type `a`, and returns a new list with entries of the same type. Let's specialize this with `a` as our `Entry` type:
Expand All @@ -398,15 +415,58 @@ This brings the two arguments `entry` and `book` into scope – on the left-hand

## Curried Functions

Functions in PureScript take exactly one argument. While it looks like the `insertEntry` function takes two arguments, it is an example of a _curried function_.
Functions in PureScript take exactly one argument. While it looks like the `insertEntry` function takes two arguments, it is an example of a _curried function_. In PureScript, all functions are considered curried.

Currying means converting a function that takes multiple arguments into a function that takes them one at a time. When we call a function, we pass it one argument, and it returns another function that also takes one argument until all arguments are passed.

For example, when we pass `5` to `add`, we get another function, which takes an int, adds 5 to it, and returns the sum as a result:

```haskell
add :: Int -> Int -> Int
add x y = x + y

addFive :: Int -> Int
addFive = add 5
```

`addFive` is the result of _partial application_, which means we pass less than the total number of arguments to a function that takes multiple arguments. Let's give it a try:

> Note that you must define the `add` function if you haven't already:
>
> ```text
> > import Prelude
> > :paste
>… add :: Int -> Int -> Int
>… add x y = x + y
>… ^D
> ```
```text
> :paste
… addFive :: Int -> Int
… addFive = add 5
… ^D
> addFive 1
6
> add 5 1
6
```

To better understand currying and partial application, try making a few other functions, for example, out of `add`. And when you're done, let's return to the `insertEntry`.

```haskell
{{#include ../exercises/chapter3/src/Data/AddressBook.purs:insertEntry_signature}}
```

The `->` operator in the type of `insertEntry` associates to the right, which means that the compiler parses the type as
The `->` operator (in the type signature) associates to the right, which means that the compiler parses the type as

```haskell
Entry -> (AddressBook -> AddressBook)
```

That is, `insertEntry` is a function that returns a function! It takes a single argument, an `Entry`, and returns a new function, which in turn takes a single `AddressBook` argument and returns a new `AddressBook`.
`insertEntry` takes a single argument, an `Entry`, and returns a new function, which in turn takes a single `AddressBook` argument and returns a new `AddressBook`.

This means we can _partially apply_ `insertEntry` by specifying only its first argument, for example. In PSCi, we can see the result type:

Expand Down Expand Up @@ -506,11 +566,11 @@ $ spago repl
> import Data.List
> :type filter
forall a. (a -> Boolean) -> List a -> List a
forall (a :: Type). (a -> Boolean) -> List a -> List a
> :type head
forall a. List a -> Maybe a
forall (a :: Type). List a -> Maybe a
```

Let's pick apart these two types to understand their meaning.
Expand Down
20 changes: 10 additions & 10 deletions text/chapter4.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,13 +112,13 @@ Let's look at the type of `map`:

```text
> :type map
forall a b f. Functor f => (a -> b) -> f a -> f b
forall (f :: Type -> Type) (a :: Type) (b :: Type). Functor f => (a -> b) -> f a -> f b
```

The type of `map` is actually more general than we need in this chapter. For our purposes, we can treat `map` as if it had the following less general type:

```text
forall a b. (a -> b) -> Array a -> Array b
forall (a :: Type) (b :: Type). (a -> b) -> Array a -> Array b
```

This type says that we can choose any two types, `a` and `b`, with which to apply the `map` function. `a` is the type of elements in the source array, and `b` is the type of elements in the target array. In particular, there is no reason why `map` has to preserve the type of the array elements. We can use `map` or `<$>` to transform integers to strings, for example:
Expand Down Expand Up @@ -195,7 +195,7 @@ Another standard function on arrays is the `concat` function, defined in `Data.A
> import Data.Array
> :type concat
forall a. Array (Array a) -> Array a
forall (a :: Type). Array (Array a) -> Array a
> concat [[1, 2, 3], [4, 5], [6]]
[1, 2, 3, 4, 5, 6]
Expand All @@ -209,7 +209,7 @@ Let's see it in action:
> import Data.Array
> :type concatMap
forall a b. (a -> Array b) -> Array a -> Array b
forall (a :: Type) (b :: Type). (a -> Array b) -> Array a -> Array b
> concatMap (\n -> [n, n * n]) (1 .. 5)
[1,1,2,4,3,9,4,16,5,25]
Expand Down Expand Up @@ -335,7 +335,7 @@ Just like `pure`, we can apply the `guard` function in PSCi to understand how it
> import Control.Alternative
> :type guard
forall m. Alternative m => Boolean -> m Unit
forall (m :: Type -> Type). Alternative m => Boolean -> m Unit
```

In our case, we can assume that PSCi reported the following type:
Expand Down Expand Up @@ -377,19 +377,19 @@ Start by importing the `Data.Foldable` module and inspecting the types of the `f
> import Data.Foldable
> :type foldl
forall a b f. Foldable f => (b -> a -> b) -> b -> f a -> b
forall (f :: Type -> Type) (a :: Type) (b :: Type). Foldable f => (b -> a -> b) -> b -> f a -> b
> :type foldr
forall a b f. Foldable f => (a -> b -> b) -> b -> f a -> b
forall (f :: Type -> Type) (a :: Type) (b :: Type). Foldable f => (a -> b -> b) -> b -> f a -> b
```

These types are more general than we are interested in right now. For this chapter, we can assume that PSCi has given the following (more specific) answer:
These types are more general than we are interested in right now. For this chapter, we can simplify and assume the following (more specific) type signatures:

```text
> :type foldl
-- foldl
forall a b. (b -> a -> b) -> b -> Array a -> b
> :type foldr
-- foldr
forall a b. (a -> b -> b) -> b -> Array a -> b
```

Expand Down
2 changes: 1 addition & 1 deletion text/chapter5.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ Record patterns provide a good example of an interesting feature of the PureScri
> showPerson { first: x, last: y } = y <> ", " <> x
> :type showPerson
forall r. { first :: String, last :: String | r } -> String
forall (r :: Row Type). { first :: String, last :: String | r } -> String
```

What is the type variable `r` here? Well, if we try `showPerson` in PSCi, we see something interesting:
Expand Down
4 changes: 2 additions & 2 deletions text/chapter6.md
Original file line number Diff line number Diff line change
Expand Up @@ -381,7 +381,7 @@ To see this, try using one of the standard type classes like `Semiring` in PSCi:
> import Prelude
> :type \x -> x + x
forall a. Semiring a => a -> a
forall (a :: Type). Semiring a => a -> a
```

Here, we might have annotated this function as `Int -> Int` or `Number -> Number`, but PSCi shows us that the most general type works for any `Semiring`, allowing us to use our function with both `Int`s and `Number.
Expand Down Expand Up @@ -540,7 +540,7 @@ This hint is enough for the compiler to infer the correct type for our generic t

```text
> :type genericTail
forall stream element. Stream stream element => stream -> Maybe stream
forall (stream :: Type) (element :: Type). Stream stream element => stream -> Maybe stream

> genericTail "testing"
(Just "esting")
Expand Down
Loading

0 comments on commit 93f2c77

Please sign in to comment.