PureScript by Example 3 章の後半を読む

この記事は [(bouzuya) PureScript Advent Calendar 2016](http://www.adventar.org/calendars/1494) の 8 日目です。 (bouzuya) PureScript Advent Calendar 2016 は bouzuya の PureScript 学習の記録です。

- ← 7 日目『 [PureScript by Example 3 章の前半を読む - Qiita][2016-12-07] 』
- → 9 日目『 [PureScript IDE (Visual Studio Code 拡張) の紹介 - Qiita][2016-12-09] 』

## 概要

昨日に続き [PureScript by Example][purescript-by-example] と [PureScript Language Guide][language-guide] を見ながら進めていきます。今日は [PureScript by Example の 3 章][purescript-by-example-3] の後半を読みます。

※注意事項: 英語と日本語訳では大きな差異があります。`0.10.x` に対応している英語の側で進めます。

- [PureScript by Example][purescript-by-example] 2016-12-05 時点で 0.10.x 向け
- [実例による PureScript][purescript-by-example-ja] 2016-12-05 時点で 0.7 向け
- [PureScript Language Guide][language-guide] 対象バージョン不明

## PureScript by Example 3.6

`type` キーワードで型を定義していきます。`type` は型シノニム (type synonyms) を定義します。

Language Guide:

- [Type Synonyms - Language Guide][language-guide-type-synonyms]

```
type Entry =
  { firstName :: String
  , lastName  :: String
  , address   :: Address
  }
type Address =
  { street :: String
  , city   :: String
  , state  :: String
  }
type AddressBook = List Entry
```

ここでは `firstName` & `lastName` & `address` からなるレコードに `Entry` という名前の型シノニムを定義しています。レコードがレコードを含んでいます。

Language Guide の例を見ると `type Bar a = { foo :: a, bar :: a }` のような定義もできるようですね。へえ。

`List Entry` と `Array Entry` は別物ですって。そりゃあ、そうでしょうね。

## PureScript by Example 3.7

`List` は型コンストラクタ (type constructor) の例だそうです。名前のとおり型を構築するんでしょう。上記の `AddressBook` で使った `List Entry` は `List a` が `Entry` に適用されたもの。

種 (kind) の話ですね。

```
$ npm run pulp:psci

> chapter3@1.0.0 pulp:psci /Users/bouzuya/.ghq/github.com/paf31/purescript-book/exercises/chapter3
> pulp psci

impPSCi, version 0.10.2
Type :? for help

> import Data.List
> Nil :: List
Error found:
in module $PSCI

  In a type-annotated expression x :: t, the type t must have kind *.
  The error arises from the type

    List

  having the kind

    * -> *

  instead.

in value declaration it

See https://github.com/purescript/purescript/wiki/Error-Code-ExpectedType for more information,
or to contribute content related to this error.

> :kind Int
*
> :kind List
* -> *
> :kind List Int
*
```

関数適用みたいですね。`List` は型引数をひとつとって型を返すわけですね。

Language Guide:

- [Kind System - Language Guide][language-guide-kind-system]

PureScript ではほかにも `!` や `#` があるんですね。また後で出てくるみたいです。

## PureScript by Example 3.8

```
showEntry :: Entry -> String
showEntry entry = entry.lastName <> ", " <>
                  entry.firstName <> ": " <>
                  showAddress entry.address
```

型を書いてから、関数を書いていますね。実装は `(<>)` で `String` をくっつけているだけですね。

`showAddress` も大差ないですね。スルー。

## PureScript by Example 3.9

`psci` で `showAddress` と `showEntry` をためしていますね。スルー。

```
$ npm run pulp:psci

> chapter3@1.0.0 pulp:psci /Users/bouzuya/.ghq/github.com/paf31/purescript-book/exercises/chapter3
> pulp psci

PSCi, version 0.10.2
Type :? for help

> import Data.AddressBook
> let address = { street: "123 Fake St.", city: "Faketown", state: "CA" }
> showAddress address
"123 Fake St., Faketown, CA"

> let entry = { firstName: "John", lastName: "Smith", address: address }
> showEntry entry
"Smith, John: 123 Fake St., Faketown, CA"
```

## PureScript by Example 3.10

```
emptyBook :: AddressBook
emptyBook = empty
```

当然ですけど `emptyBook` は空の List にするみたいです。

```
insertEntry :: Entry -> AddressBook -> AddressBook
insertEntry entry book = Cons entry book
```

`psci` で `Cons` の型を確認して、想定した型と一致することを確認していますね。

```
> import Data.List
> :type Cons
forall t1. t1 -> List t1 -> List t1
```

`AddressBook` は `List Entry` なので `t1` を `Entry` だと解釈すれば一致します。

## PureScript by Example 3.11

何度かスルーしたカリー化ですね。PureScript の関数はカリー化された形であり、ひとつの引数しか取れません。複数の引数のようなものが必要な場合には、次の引数をとる関数を返します。

たとえば `insertEntry` は `Entry -> AddressBook -> AddressBook` なので、`Entry` をとって `AddressBook -> AddressBook` という関数を返します。返されたこの関数はさらに `AddressBook` をとって `AddressBook` を返します。型のとおりですし、関数の引数は常にひとつです。この例のように、関数の一部の引数だけを渡すことを部分適用と言います。

```
> let john = { firstName: "John", lastName: "Smith", address: address }
> let insertJohn = insertEntry john
```

`insertJohn` は部分適用した結果、得られた関数です。`john` を insert する関数です。 (そんなに john ばかり要らない気はしますが……)

PureScript by Example では加えてイータ変換に触れています。

```
insertEntry :: Entry -> PhoneBook -> PhoneBook
insertEntry entry book = Cons entry book
```

これは

```
insertEntry :: Entry -> PhoneBook -> PhoneBook
insertEntry = Cons
```

と同じ動きをします。このように同じ引数なので、わざわざ指定しなくても良いのです。

この形式には利点があるのでしょうが、個人的には引数を明示しているほうが読みやすいように感じます。型を見ないと何を取るのかもよく分かりませんし……。まだ慣れていないだけかもしれません。

## PureScript by Example 3.12

`List` のための関数を組み合わせて `AddressBook` から `Entry` を探す関数を定義するようです。

```
> import Data.List
> :t filter
forall a. (a -> Boolean) -> List a -> List a

> :t head
forall a. List a -> Maybe a
```

`filter` はカリー化された 2 引数の関数ですね。第 1 引数の関数で第 2 引数の `List` を filter して、その結果である `List` を返すみたいですね。

`head` は `List` の先頭要素を返します。

Pursuit:

- [filter - Data.List - purescript-lists - Pursuit][pursuit-filter]
- [head - Data.List - purescript-lists - Pursuit][pursuit-head]

`a` をそれぞれ AddressBook における要素に置き換えると次のようになります。

```
filter :: (Entry -> Boolean) -> AddressBook -> AddressBook
head :: AddressBook -> Maybe Entry
```

これらを使って `findEntry` をつくります。

```
findEntry :: String -> String -> AddressBook -> Maybe Entry
findEntry firstName lastName book = head $ filter filterEntry book
  where
    filterEntry :: Entry -> Boolean
    filterEntry entry = entry.firstName == firstName && entry.lastName == lastName
```

`($)` や `where` が初出なんですけど、概要としては `filter` したものを `head` しているだけです。

`where` で補助関数として `filterEntry` を定義しています。`findEntry` の引数に渡された `firstName` と `lastName` に一致する `Entry` かを `Boolean` で返す関数ですね。

## PureScript by Example 3.13

中置演算子 (infix operator) ・中置関数 (infix function) の話です。上記の `($)` に関連した話です。

Pursuit:

- [($) - Data.Function - purescript-prelude - Pursuit][pursuit-apply]

`head $ filter filterEntry book` は `head (filter filterEntry book)` と同じ動きをします。

`($)` は `apply` の alias で次のように定義されています。

```
apply :: forall a b. (a -> b) -> a -> b
apply f x = f x

infixr 0 apply as $
```

`($)` は右結合の低い優先順位の演算子として定義されています。 `apply` は第一引数の関数を第二引数の値に適用する関数です。

`($)` を使えば括弧の入れ子を解消できます。

```
street (address (boss employee))
street $ address $ boss employee
```

なるほど、便利ですね。

ちなみに関数を演算子的に使うことも、その逆もできます。演算子を関数的に使う場合は `()` で括り、関数を演算子的に使う場合は ` で括れば良いです。

```
npm run pulp:psci
> chapter3@1.0.0 pulp:psci /Users/bouzuya/.ghq/github.com/paf31/purescript-book/exercises/chapter3
> pulp psci

PSCi, version 0.10.2
Type :? for help

> import Prelude ((+))
> 1 + 2
3

> (+) 1 2
3

> let f = (+)
> f 1 2
3

> 1 `f` 2
3
```

Language Guide:

- [Functions as Operators - Language Guide][language-guide-functions-and-operators]
- [User-defined operators - Language Guide][language-guide-user-defined-operators]
[https://github.com/purescript/purescript/wiki/Language-Guide#functions-as-operators]

## PureScript by Example 3.14

関数合成の話ですね。`>>>` と `<<<` です。それぞれ `compose` と `composeFlipped` の alias です。

Pursuit:

- [(<<<) - Control.Semigroupoid - purescript-prelude - Pursuit][pursuit-compose]
- [(>>>) - Control.Semigroupoid - purescript-prelude - Pursuit][pursuit-compose-flipped]

前述の `findEntry` であれば次のように書き換えられます。

```
head $ filter filterEntry book
(head <<< filter filterEntry) book
(filter filterEntry >>> head) book
```

`insertEntry = Cons` のように `book` を削れば、`findEntry firstName lastName = head <<< filter filterEntry` とできます。ふむふむ。

## PureScript by Example 3.15

`psci` でためしていますね。

`Show` インスタンスでないと表示できない箇所は `map showEntry entry` のようにして回避しているようです。古い分だと `(<$>)` よりも読みかたの分かる `map` になったということでしょうか。

Pursuit:

- [map - Data.Functor - purescript-prelude - Pursuit][pursuit-map]
- [(<$>) - Data.Functor - purescript-prelude - Pursuit][pursuit-map-op]

## まとめ

3 章の後半は密度が高かったです。慌てて進めたので、雑になってしまいました。

## 参考

- [Language Guide - Functions as Operators][language-guide-functions-and-operators]
- [Language Guide - Kind System][language-guide-kind-system]
- [Language Guide - Type Synonyms][language-guide-type-synonyms]
- [Language Guide - User-defined operators][language-guide-user-defined-operators]
- [Language Guide](https://github.com/purescript/purescript/wiki/Language-Guide)
- [PureScript by Example - 3. Functions and Records][purescript-by-example-3]
- [PureScript by Example][purescript-by-example]
- [Pursuit - (<$>) - Data.Functor - purescript-prelude][pursuit-map-op]
- [Pursuit - (<<<) - Control.Semigroupoid - purescript-prelude][pursuit-compose]
- [Pursuit - (>>>) - Control.Semigroupoid - purescript-prelude][pursuit-compose-flipped]
- [Pursuit - filter - Data.List - purescript-lists][pursuit-filter]
- [Pursuit - head - Data.List - purescript-lists][pursuit-head]
- [Pursuit - map - Data.Functor - purescript-prelude][pursuit-map]
- [Pursuit][pursuit]
- [paf31/purescript-book][]

## 次回以降の TODO

- PureScript by Example & PureScript Language Guide を読み進める
- Tagged Unions / Newtype
- `psc-package`
- PureScript IDE の導入
- [24 Days of PureScript, 2016][paf31/24-days-of-purescript-2016]
- [24 Days of PureScript, 2014](https://gist.github.com/paf31/8e9177b20ee920480fbc)
- 某氏の記事の紹介
- [github.com/purescript](https://github.com/purescript/) のコードを読む
- [github.com/purescript-node](https://github.com/purescript-node) のコードを読む
- [github.com/purescript-contrib](https://github.com/purescript-contrib) のコードを読む

[2016-12-01]: http://qiita.com/bouzuya/items/a5be85ec3e496e5bf436
[2016-12-02]: http://qiita.com/bouzuya/items/806335ea74ead0e33d95
[2016-12-03]: http://qiita.com/bouzuya/items/86cc9aaaddcc1aa790cf
[2016-12-04]: http://qiita.com/bouzuya/items/b2b80c22c50d0c1e739c
[2016-12-05]: http://qiita.com/bouzuya/items/f19ee0b86e9aec3f0afa
[2016-12-06]: http://qiita.com/bouzuya/items/4694ac25f68931115319
[2016-12-07]: http://qiita.com/bouzuya/items/62c6ad30ecba3c68a7a3
[2016-12-09]: http://qiita.com/bouzuya/items/868eafc19b96619c025a
[paf31/24-days-of-purescript-2016]: https://github.com/paf31/24-days-of-purescript-2016
[paf31/purescript-book]: https://github.com/paf31/purescript-book/tree/8ad7d2af75ad4c795d5075018e9ee45005799e6f
[pursuit]: https://pursuit.purescript.org/
[pursuit-apply]: https://pursuit.purescript.org/packages/purescript-prelude/2.1.0/docs/Data.Function#v:($)
[pursuit-compose]: https://pursuit.purescript.org/packages/purescript-prelude/2.1.0/docs/Control.Semigroupoid#v:(%3C%3C%3C)
[pursuit-compose-flipped]: https://pursuit.purescript.org/packages/purescript-prelude/2.1.0/docs/Control.Semigroupoid#v:(%3E%3E%3E)
[pursuit-filter]: https://pursuit.purescript.org/packages/purescript-lists/3.2.1/docs/Data.List#v:filter
[pursuit-head]: https://pursuit.purescript.org/packages/purescript-lists/3.2.1/docs/Data.List#v:head
[pursuit-map-op]: https://pursuit.purescript.org/packages/purescript-prelude/2.1.0/docs/Data.Functor#v:(%3C$%3E)
[pursuit-map]: https://pursuit.purescript.org/packages/purescript-prelude/2.1.0/docs/Data.Functor#v:map
[purescript/purescript-math]: https://github.com/purescript/purescript-math
[purescript-by-example]: https://leanpub.com/purescript/read
[purescript-by-example-3]: https://leanpub.com/purescript/read#leanpub-auto-functions-and-records
[purescript-by-example-ja]: http://hiruberuto.bitbucket.org/purescript/
[language-guide]: https://github.com/purescript/purescript/wiki/Language-Guide
[language-guide-type-synonyms]: https://github.com/purescript/purescript/wiki/Language-Guide#type-synonyms
[language-guide-kind-system]: https://github.com/purescript/purescript/wiki/Language-Guide#kind-system
[language-guide-functions-and-operators]: https://github.com/purescript/purescript/wiki/Language-Guide#functions-as-operators
[language-guide-user-defined-operators]: https://github.com/purescript/purescript/wiki/Language-Guide#user-defined-operators