「すごいHaskellたのしく学ぼう!」を社内で輪読会したのでいくつかメモ

数ヶ月ほど前に、社内で「[すごいHaskellたのしく学ぼう!](https://amzn.asia/d/fej88n7)」を輪読会しました。

https://www.amazon.co.jp/dp/4274068854

輪読会を通して気付いた点を本記事にて紹介します。


## 「すごいHaskellたのしく学ぼう!」はどんな本なのか？

この本、表紙とタイトルそしてゲームキャラが豊富な挿絵で非常に初歩的な内容なのかと思いがちですが、内容はHaskellの初歩から始まり、ファンクター・モノイド・アプリカティブファンクター・IO以外のモナド・モナド則などHaskellで学びたい内容が網羅されている本格的な本です。

**Haskell入門書としてプログラミング中級者にオススメです。**

特にモナドについては、普通の本だと `do` 構文で `IO` モナド ＋ α で終わりということも少なくないと思われるところ、きちんと `IO` 以外のモナドが説明されており、（ML系の）関数型言語の入門書としては大変に満足いくものでした。


## 読了までにかかった時間

読了まで、だいたい営業日に20分程度の輪読(+音読)で3ヶ月程度かかりました。

中盤(7章あたり)から内容が難しくなるので、あまり駆け足のペースで行わないほうがいいと感じました。


## Haskell の環境整備方法

Haskellを動作させるためのツールチェインのインストーラとしては [GHCup](https://www.haskell.org/ghcup/) がおすすめです。Haskellの各種ツールをバージョンを選択しながらインストールできます。

Macの場合は、GHCupをHomebrewでインストールすることもできますが、[公式サイトの手順](https://www.haskell.org/ghcup/install/)どおりに直接シェルスクリプトで入れたほうが混乱が少ないように感じました。

```bash
# GHCupのインストール (※公式を参照してください)
$ curl --proto '=https' --tlsv1.2 -sSf https://get-ghcup.haskell.org | sh
# ツールの選択とインストール (recommended になっているものを選択してインストール＆選択)
$ ghcup tui

# 万が一、構築した環境がなにかおかしくなってしまったら、↓で全てGHCupのインストールをやり直すことも可能
# $ ghcup nuke
```

本のコードを打ち込むだけであれば、だいたいは上記インストール後にGHCiが動けば問題なく検証できます。

一方で、込み入った検証を行いたい場合もあると思われるので、学習用のHaskellプロジェクトを一つ作成したほうがいいです。Stackを用いてプロジェクトを作成します。

```bash
# Haskellプロジェクトを作成
$ stack new haskell-rindoku

# VS Codeの設定 (プラグインのインストール)
$ code --install-extension haskell.haskell
# VS Codeでプロジェクトを開く
$ code haskell-rindoku
```

```haskell:app/Main.hs
module Main (main) where

import Lib

main :: IO ()
main = putStrLn "Hello, World"
```

```bash
# プロジェクト設定でのGHCiの起動 （GHCiのインストールも自動で行われる）
$ stack repl

ghci> main
Hello, World

# こちらでも起動可能
$ stack ghci
```

HaskellではStackによりプロジェクト環境のコントロールが行われるようです。より深く知りたい場合は[Stackのドキュメント](https://docs.haskellstack.org/en/stable/) に軽く目を通しておくといいかもしれません。


### [random](https://hackage.haskell.org/package/random) パッケージ (`System.Random`) は今のHaskellには無い

本書の途中、 `Random` 型クラスなど `System.Random` を参照している箇所がありますが、今のHaskellでは標準に `random` パッケージはインストールされていません。

```
ghci> import System.Random

<no location info>: error:
    Could not find module ‘System.Random’
    It is not a module in the current program, or in any known package.
```

https://zenn.dev/autotaker/articles/random-1-2-is-awesome

学習目的で読んでいるだけなので、 `package.yaml` に追記して利用しましょう。

```yaml:package.yaml
# dependencies のリストに random を追加
dependencies:
- base >= 4.7 && < 5
- random # これを追加
```

```bash
# 上記の package.yaml 追記後に stack repl すると自動的に使えるようになる
$ stack repl

ghci> import System.Random
ghci> :i Random
type Random :: * -> Constraint
class Random a where
  randomR :: RandomGen g => (a, a) -> g -> (a, g)
  ...
```

## 各記号・演算子の読み方

Haskellは特に記号が多いので、本の内容を声に出して読む際に、各記号の日本語としての読み方に非常に混乱・苦慮すると思います。

輪読会の中で記号の呼び方を統一すると便利です。今回は以下のように統一しました。

- `\`: 「ラムダ (lambda)」 または 「バックスラッシュ (back slash)」
- `foldl'` (関数名などの後ろのクォート): 「foldl プライム (prime)」
- `(Num a) =>`: 「Num a として」
- `->`: 「アロー (arrow)」
- `<-`: 「バインド (bind)」
- `[a b c]`: 「リスト (list) a b c」
- `(a b c)`: 「タプル (tuple) a b c」
- `()`:　「ユニット (unit)」
- `<*>`: 「アプライ (apply)」
- `<$>`: 「ダラー (dollar)」
- `>>`: 「ゼン (then)」 
- `>>=`: 「バインド　(bind)」
- `<=<`: 「フィッシュ (fish)」
- `[]`: 「角カッコ」・「スクエアブラケット (square bracket)」
- `{}`: 「波カッコ」・「カーリーブラケット (curly bracket)」
- `<>`: 「山カッコ」・「アングルブラケット (angle bracket)」

音声だけで相手に正確な情報を伝えなければならないというわけでもないので、厳密な方針を貫く必要はないと考えます。


## 理解が難しかったところ・理解のヒント

やはりファンクターあたりが一番難しいと思います。

### 関数 `(r) -> *` のファンクターやモナドが分かりづらい

書籍中でも書かれていますが、 `(r) -> *` のファンクターのイメージはかなり初心者に分かりづらいと思います。

こういうふうに説明した記憶がありますが、正直伝わったとも思えず……

「[`Maybe a`](https://wiki.haskell.org/Maybe) は `a` という型が、あったりなかったりする箱（文脈）だよね。 `pure`で箱に入るよね。」
「同様に `(r) -> *` は関数の始域の型が `r` になっている箱（文脈）だよね。 `pure` で始域 `r` な関数だけど定数を返す関数に入るね。」

概念的なイラストもiPadで描いてみました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1577592/fab3b47e-c01c-7fec-509d-8f6bbc0e0add.png)

最終的には分からないことをあまり気にしないほうがいいというアドバイスもしました。（そういいつつ、最後あたりはこの辺を理解の前提としている節もあり、ちょっとつらいのですが）


### 圏論 （Hask圏）

「そもそも*ファンクター*ってなんだ？ `fmap` ってなんだ？用語と定義が覚えづらいぞ」 と思うのは私だけでしょうか？

これらの概念の元をたどれば数学の圏論というものに根ざすらしいのですが、本書ではあまりそこに触れられません。（本書のいいところでもあります）

そこをあえて少しだけでいいので、**圏論的な定義に自主的に触れてみると、案外理解がすんなり進むのではないかな**と思いました。

実際に輪読会最中に以下のような板書で説明したりしました。（かなり走り書きなので読めるものではないと思いますが）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1577592/4ae2ac27-fb63-58e6-2e4f-a7cacc4dc7fe.png)

「Haskellでは*Hask圏*というものを使っており、その圏の中では*対象*（型＝値の集合）同士に*射*（関数）が定義されているが、*ファンクター*（関手）というものを使うと、対象と射をその関係を保ったままコピーすることができるよ。たとえば、Maybeというファンクターはそうなっているね。」

「*モナド* はモノイド的な条件を満たしたファンクターだよ」 [^1]

[^1]: かの有名な[「モナドは単なる自己関手の圏におけるモノイド対象だよ。何か問題でも？」 ](http://www.aoky.net/articles/james_iry/brief-incomplete-and-mostly-wrong.htm) に近いことを言っていたことに、これを書いてから気づきました

またしても初学者に伝わるかどうかは微妙ですが、ヒントとしてはアリと思います。

YouTubeにHaskell初学者向けの圏論講座もたくさんありました。そういった動画への導線も作ってあげるといいと思います。

<iframe width="560" height="315" src="https://www.youtube.com/embed/I6HxnynPkSA?si=jPcnJUhX4JXyyjxB" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>

### その他分かりづらいという感想があったところ

- 非決定性の計算(リストモナド)が分かりづらい
- モナド則の説明や `do` 構文からの書き換えが分かりづらい

（なんか他にも色々あった気がする……）


## まとめ

Haskellといえば、一度は学習してみたい（学習してほしい）言語の筆頭だと個人的に考えているのですが、そのHaskellへの入門として「[すごいHaskellたのしく学ぼう!](https://amzn.asia/d/fej88n7)」はちょうど良い本と感じました。[^2]

[^2]: 正直Scala・F#・OCamlなどもっと実用的な（ML系）関数型言語はありますが、尖っている＆そこそこプレイヤーがいるHaskellはそれらより学習対象として相応しいと考えています。（個人の感想です）