Haskell入門ハンズオン! #5 - 当日用資料 (1/5)

Haskell入門ハンズオン! #5 - 当日用資料 (1/5)
=====================================================================

はじめに
-------

この記事は2019.2.2 Sat.の[Haskell入門ハンズオン! #5](
https://shinjukuhs.connpass.com/event/105071/
)の当日用の資料です。基本的にスライドの内容とおなじ内容になっています。

講師紹介
-------

[「Haskell - 教養としての関数型プログラミング」](https://www.amazon.co.jp/Haskell-%E6%95%99%E9%A4%8A%E3%81%A8%E3%81%97%E3%81%A6%E3%81%AE%E9%96%A2%E6%95%B0%E5%9E%8B%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0-%E9%87%8D%E5%9F%8E%E8%89%AF%E5%9B%BD/dp/4798048062)の著者。

![cover.png](https://qiita-image-store.s3.amazonaws.com/0/93927/2fc93d8d-c9a1-354d-c848-7479c3940873.png)

### コンピュータとの出会い

コンピュータとの出会いはMSX。入部したコンピュータクラブに何台かMSXが置いてあった。

![msx.JPG](https://qiita-image-store.s3.amazonaws.com/0/93927/7be93a08-0c6f-826f-da1b-c33c16d7581f.jpeg)

#### MSXとは

2006年にMacがPowerPCからPC/AT互換機になってから、一般的なパソコンは、ほぼPC/AT互換機になった。それよりも、ずっとむかし、パソコンのアーキテクチャが群雄割拠だった時代(1980年代)、マイクロソフトとアスキーが提唱したパソコンのアーキテクチャ(1983)。時代の波に乗れずに消えていってしまった。MSXにはROM上にBASICが内蔵されていた。BASIC言語によるプログラミングは、つぎのような感じ(Wikipediaより)。

```
10 REM 5つ数える("3"だけ飛ばす)
20 FOR I = 1 TO 5
25 IF I = 3 THEN GOTO 40
30 PRINT I
40 NEXT
RUN
```

基本的にGOTO文を多用する。行番号を指定して、そこに飛ぶというやりかた。

### 言語、OSなど

コンピュータクラブに所属していたころは、BASICを(MSXの台数が不足していたので)紙に書いていた。

大学時代にPerlに出会う。ホームページにアクセスカウンターや掲示板をつけたかったので。その後、C言語、Ruby、Python、Common Lisp、Schemeなどなどに手を出した。青木峰郎「Rubyソースコード完全解説」の「(Rubyのソースコードは)少なくともHaskellやPL/Iで書いてあるわけではないので一般人にも読める可能性が高い」という一文から、興味をもち「Haskell: The Craft of Functional Programming」を購入。Haskellの魅力にはまる。以降はHaskell一筋だ。

だいたい、つぎのようになる。

* Haskell歴 16年
* Gentoo歴 17年
* Vim派
* 漢字直接入力(TUT-code)
* シェルはZsh
* キーボードはHHKB

![hhkb2_croped.png](https://qiita-image-store.s3.amazonaws.com/0/93927/3c805c88-4e94-0a1d-fa20-c07bb655920d.png)

### コーヒー

最近は、すこしだけ、コーヒーにこっている。毎朝、エスプレッソを抽出してカフェラテを飲んでいる。

![cafelatte_croped.jpg](https://qiita-image-store.s3.amazonaws.com/0/93927/1f5d3fff-08d2-334c-e3d0-86c5bd46c1b3.jpeg)

焙煎用の網で自家焙煎もしている。

![coffee_beans_croped.jpg](https://qiita-image-store.s3.amazonaws.com/0/93927/2aaeb02e-c86d-377c-bfe3-69875ff79ac4.jpeg)

Haskellについて
--------------

### Haskellは楽しい

* Haskellを学ぶのは楽しい。
* Haskellでコードを書くのは楽しい。
* Haskellでコードを書くとき、安心感がある。

Haskellを学ぶ理由として、それだけでも十分かなと思う。Haskellは手になじむ道具だ。実際にあるていど学ぶことで、その楽しさや安心感がわかると思う。「Haskellのよさ」はこの感覚だ。この感覚をつたえることは難しい。でも、不十分ながらもHaskellの何がいいのかを説明してみよう。

Haskellの、どこがいいのだろうか。

* 安心感
* 適切な抽象化による二重化の回避
* ムダのない文法

そのあたりだろうか。

### Haskellは、どんな言語?

Haskellは、どんな言語だろうか。実際に書くまえに、言語を紹介するのはむずかしい。ここでは「手続き」と「関数」というキーワードで説明していく。

* 手続き: メモリなどの「状態」を変化させるもの
    + 「手続き」は「実行」されて、結果として状態が変化する
* 関数: 「引数」をあたえられて「式」になる
    + 「式」は「評価」されて「値」になる

多くの言語で「手続き」と「関数」は、わけられていない。たとえば、つぎのような(Rubyの)コードを考える。

```ruby
$number = 123

def add3(x)
        $number += 321
        return(x + 3)
end
```

このメソッドadd3は「状態を変化させる」ので「手続き」であると考えられる。しかし、add3(5)は評価されて8になるという点で、このメソッドは「関数」であるとも言える。

このように、「手続き」のようでもあり「関数」のようでもあるナニカ。そのナニカが、ほとんどの言語において、処理の記述に使われている。このナニカの問題は「関数」とはちがって、単純な「置き換え」で理解できないことだ。コードのなかのadd3(5)を8に置き換えることはできない。これは「コードが何を意味するのか」を簡単には把握できないことを意味する。

Haskellでは「関数」と「手続き」とは、きちんとわけられている。「関数」は状態を変化させないし、状態の影響をうけない。「手続き」は「関数」の引数になり、返り値にもなる。つまり、Haskellでは「手続き」は、ほかの「値」とおなじように、関数によって処理されるモノだ。

ほかの言語では「状態変化」が頻繁に使われる。「状態」は時間の関数であり、タイミングによって変化してしまう、あつかいづらいものだ。必要のないところで「状態変化」を使うことは、ムダに話を複雑にしてしまう。

### とはいえ、Haskellの何がいいのかを説明するのは

Haskellのよさを言葉にするのはむずかしい。実際にコードを書くまで、わからないところがある。Haskellを学ぶことで、ほかの言語を使ったとしても、よりきれいで安全な書きかたをするクセがつく。高度な抽象化が可能なので、コードの二重化などを避けることができる。ムダのない気持ちのいいコードが書ける。

### 実際に使われてるの?

Haskellは、いろいろなところで使われている。たとえば、以外のような会社で使われている。

* Facebook (SNS)
* NTTデータ (データ通信、システム構築)
* ASAHIネット (プロバイダ)
* Tsuru Capital (高頻度取引)

それぞれ、つぎのような用途で使っている。

* スパム対策
* COBOL資産の分析
* 認証サーバ
* 全般

「既存のライブラリを使えばすむ」のではなく、それぞれの問題にあわせた解決が必要なとき、「複雑さ」を制御しながら解を組み立てていく。そのような場面がHaskellにむいていると感じる。また、Tsuru Capitalでは「Haskellを使えば優秀な人材が集まる」といった戦略もあった、とか。これは、勉強会を主催するうえで、僕自身が感じていることでもある。

このハンズオンについて
------------------

このハンズオンでは、実際にHaskellのコードを動かしてみる。はじめのうちは、対話環境で式の打ち込みと結果の表示を試す。

また、演習として課題のコードを書いてもらい模範解答の解説もしようと思う。

あとのほうで、独立して動作するアプリケーションを作ってみる。最後に自分達で考えたプログラムを作ってもらうことになる。

時間に限りがあるので、すべてを説明することはできない。「できるだけ理解しやすいように、また、自分でちょっとしたコードが書けるように」と考えた。Haskellは「難しいこと」をするときに、本当の力を発揮する。なのでHaskellの魅力を十分にはつたえられていないかもしれない。

対話環境
-------

「値」「関数」「型」について学ぶ。ここでは、対話環境に値を打ち込みながら学んでいく。

### ディレクトリの用意

バージョンのちがいなどを避けるために、こちらで用意したディレクトリを使用する。gitが使える人は、つぎのようにする。

```terminal
% git clone https://github.com/YoshikuniJujo/haskell-nyumon-handson-work.git
```

%はプロンプトを意味する。入力しない。gitが使えない人は、つぎのアドレスからZIP圧縮されたファイルをダウンロードして解凍する。

```
https://bit.ly/2RUIS2E
```

それぞれのOSのやりかたで解凍する。Unix系OSの例を示す。

```terminal
% unzip haskell-nyumon-handson-work-master.zip
```

それぞれのOSのやりかたで名前を含える。

```terminal
% mv haskell-nyumon-handson-work-master haskell-nyumon-handson-work
```

(それぞれのOSのやりかたで)ディレクトリを移動する。

```terminal
% cd haskell-nyumon-handson-work/work-d20190202
```

対話環境は、つぎのようにして立ち上げる。

```
% stack ghci
(何行かのメッセージ)
*Main Lib>
```

%はZshでは標準的なプロンプト。入力しない。*Main Lib>は対話環境のプロンプト、これからは省略して'>'のみで示すことにする。

値と型
-----

とりあえず、対話環境に「値」を打ち込んでみよう。

```
> 123
123
> 'c'
'c
> True
True
```

値には「型」がある。

```
> :type 'c'
'c' :: Char
> :type True
True :: Bool
```

四則演算もできる。

```
> 3 + 5
8
> 12 - 7
5
> 3 * 12
36
> 38 `div` 4
9
> 38 / 4
9.5
```

整数のわり算と実数のわり算とは、意味がちがうので別になっている。

対話環境では直前の結果を、変数itによって再利用できる。

```
> 12345 * 67890
838102050
> it * 5
4190510250
```

変数it以外の変数も、明示的に書けば使える。

```
> x = 123
> x * 2
246
> name = "taro"
> name
"taro"
```

変数に値をわりあてることを「[値]が[変数]を束縛する」という。ほかの言語を使える人なら、「[値]を[変数]に代入する」とおなじ意味であると考えれば(ここでは)いい。

関数と型
------

関数は引数をとる。引数をとった関数は値に評価される。

```
> :module Data.Char
> toUpper 'c'
'C'
```

関数toUpperは引数を大文字にする関数。モジュールData.Charから公開されている。関数にも型がある。

```
> :type toUpper
toUpper :: Char -> Char
```

Char型の引数をとって、Char型の値をかえす。関数の型は、引数と返り値の型を(->)でつないだもの。もうひとつの例。

```
> isUpper 'c'
False
```

関数isUpperは引数が大文字ならばTrueを、そうでなければFalseをかえす。型をみてみよう。

```
> :type isUpper
isUpper :: Char -> Bool
```

Char型の引数をとって、Bool型の値をかえす。

関数定義
-------

関数を自分で定義するには、つぎのように書く。

```
[関数名] [引数1] [引数2] ... = [式]
```

実際に定義してみよう。

```
> f x y = x + y
> f 3 4
7
```

関数をファイルから読み込むこともできる。

```
% vim samples/simpleFun.hs
```

```hs:samples/simpleFun.hs
g x y = x + y
```
ファイルに定義した関数は:loadで読み込める。

```
> :load samples/simpleFun.hs
> g 3 4
7
```

関数には型宣言をつけておいたほうがいい。

```terminal
% vim samples/simpleFun.hs
```

```hs:samples/simpleFun.hs
g :: Integer -> Integer -> Integer
g x y = x + y
```

型宣言は、つぎのようになる。

```
変数 :: 型
```

また、型IntegerはHaskellでの標準的な多倍長整数型。関数の型は、つぎのように表される。

```
引数1の型 -> 引数2の型 -> ... -> 返り値の型
```

型注釈
-----

型宣言は「変数の型」を宣言する。よって、変数を束縛しない「値」の宣言はできない。「値(式)」の型を特定したいときには「型注釈」を使う。ちゃんと理解するには「型クラス」を学ぶ必要があるが、Haskellでの数値リテラルは「多相的」だ。なので、必要に応じて、型注釈をつけて型を特定する。

```
> 123 :: Integer
123
> :type it
it :: Integer
> 123 :: Double
123.0
> :type it
it :: Double
```

型Doubleは代表的な実数型。

関数リテラル
----------

3をたす関数を考える。

```
> add3 n = n + 3
> add3 8
11
```

「3をたす関数」が変数add3を束縛している。その「3をたす関数」そのものを表すには、つぎのようにする。

```
\n -> n + 3
```

やってみよう。

```
> add3 = \n -> n + 3
> add3 8
11
```

一般的に、関数リテラルは、つぎのように書ける。

```
\引数1 引数2 ... -> 式
```

つぎのふたつの定義は、おなじことだ。

```
関数名 引数1 引数2 ... = 式
関数名 = \引数1 引数2 ... -> 式
```

うえのかたちは、したのかたちの構文糖と考えられる。関数リテラルは変数を束縛せずに使える。

```
> (\n -> n + 3) 8
11
```

演算子
-----

Haskellでは関数と演算子のちがいは構文だけだ。()と``でたがいに変換できる。

```
> mod 15 4
3
> 15 `mod` 4
3
> 12 + 25
37
> (+) 12 25
37
```

演算子を関数に変換するとき、前か後ろのどちらかの引数を、あらかじめ適用しておくことができる。これを、演算子の部分適用という。

```
> (/ 5) 65
13.0
> (65 /) 5
13.0
```

(/ 5)は何かを5でわる関数になり、(65 /)は65を何かでわる関数になる。

ガード
-----

関数を定義するときなどに、条件によって結果を変えることができる。ガードという構文が使える。

```
% vim samples/guard.hs
```

```hs:samples/guard.hs
div2 :: Integer -> Integer
div2 n  | even n = n `div` 2
        | otherwise = n
```

ガードは、つぎのような構文になる。

```
| (Bool値をかえす式) = (結果の式)
```

ガードは関数定義のときに複数つけられる。otherwiseはTrueということ。つまり、「それ以外の場合」を受け入れる。関数evenは引数が偶数のときTrueをかえす。試してみる。

```
> :load samples/guard.hs
> div2 14
7
> div2 19
19
```

Bool値をかえす関数
---------------

Bool値をかえす関数を述語とよぶ。

```
> 12 == 12
True
> 3 == 9
False
> 3 < 15
True
> 3 >= 15
False
```

いくつかの述語をみた。

if式
----

真偽値によって処理を分岐させるにはガードが使える。やっていることは、おなじだが、構文的に異なる特徴をもつのがif式。

```
> if 12 == 12 then "good" else "bad"
"good"
> if 3 >= 15 then "foo" else "bar"
"bar"
```

真偽値に対する演算
--------------

真偽値に対する演算として、つぎのみっつがある。

```
(&&), (||), not
```

```
> True && True
True
> True && False
False
> True || False
True
> False || False
False
> not True
False
```

それぞれ「かつ」「または」「...でない」。

let式
-----

局所的に変数を束縛することができる。

```
> let x = 8 in x + 5
13
```

関数適用演算子
-----------

Haskellには関数を適用する演算子($)がある。

```
> :module Data.Char
> toUpper 'x'
'X'
> toUpper $ 'x'
'X'
```

「いったい何の意味が?!!!」

ひとつには「結合力」がある。演算子($)は結合力が最弱なので

```
f $ ...
```

のように書かれていたとき、...の部分がひとかたまりで、その全体に関数fを適用するということが、すぐにわかる。試してみよう。

```
> negate 3 + 5
2
> negate $ 3 + 5
-8
```

関数show
--------

対話環境で評価の結果を「表示」してきた。「表示」には内部では「文字列化」関数showが使われている。関数showは「表示可能」な値を「文字列」に変換する。

```
> 123
123
> show 123
"123"
```

ふたつめでは、showによって表示用の文字列に変換された文字列が、さらに、暗黙のshowで、表示用に変換された。

エラーを発生させる
---------------

未定義を意味する値undefinedがある。コードを書いている途中で、「あとで定義しよう」というときに使える。

```
> undefined
*** Exception: Prelude.undefined
...
```

値undefinedを評価しようとすると例外が発生する。メッセージを指定して例外を発生させたいときもある。

```
> error "Oh, my god!"
*** Exception: Oh, my god!
...
```

なんらかの理由で定義できない値について、説明つきのエラーを発生させるのに使える。

再帰関数
-------

関数を定義するときに定義のなかに、その関数自体が出てくる。そのような関数を再帰関数と呼ぶ。

0からnまでの整数の和を計算する関数。

```
% vim samples/rec.hs
```

```hs:samples/rec.hs
sum123 :: Integer -> Integer
sum123 0 = 0
sum123 n = sum123 (n - 1) + n
```

対話環境で試してみよう。

```
> :load samples/rec.hs
> sum123 8
36
> sum123 100
5050
```

定義を再掲する。

```
sum123 0 = 0
sum123 n = sum123 (n - 1) + n
```

n = 3について展開してみよう。

```
sum123 3
        => sum123 2 + 3
        => (sum123 1 + 2) + 3
        => ((sum123 0 + 1) + 1) + 2) + 3
        => ((0 + 1) + 2) + 3
        => 6
```

ここまでの、まとめ
---------------

値、型、関数、再帰関数について学んだ。値には型がある。型は文字型Char、整数型Integer、真偽値型Boolなど。引数をとって返り値をかえすのが関数だ。関数にも型がある。

```
[引数1の型] -> [引数2の型] -> ... -> [返り値の型]
```

ファイルに定義した関数を読み込むには:loadコマンド。

定義に自分自身を使うのが再帰関数。例として、0からnまでの和を求める関数を示した。再帰関数を使えば「くりかえし」を表現できる。

タプル、Maybe値、リスト
--------------------

Haskellで、よく使われるデータ構造を学ぶ。

* タプル
* Maybe値
* リスト

タプル
-----

ふたつの値をペアにしたいときに使う。

```
> (123, 'c')
(123,'c')
```

ペアにした値の、それぞれを取り出したいときは、パターンマッチを使う。

```
> f (n, c) = 2 * n
> f (123, 'c')
246
```

型宣言は、つぎのようになる。

```
p :: (Integer, Char)
p = (123, 'c')
```

Maybe値
-------

値が「ない」かもしれないときに使う。

```
% vim samples/maybe.hs
```

```hs:samples/maybe.hs
safeDiv :: Integer -> Integer -> Maybe Integer
safeDiv _ 0 = Nothing
safeDiv a b = Just (a `div` b)
```

'_'(アンダースコア)は、引数を使用しないときに使う。「ワイルドカード」と呼ぶ。関数divは整数の除算を行う。``(バッククォート)でかこむと関数は演算子になる。除数が0のとき結果はない(Nothing)。そうでないときは、結果をJustでくるむ。試してみよう。

```
> :load samples/maybe.hs
> safeDiv 35 4
Just 8
> safeDiv 15 0
Nothing
```

Maybe値のなかみを使いたいときは、パターンマッチを使う。

```
% vim samples/maybe.hs
```

```hs:samples/maybe.hs
addToMaybe :: Maybe Integer -> Integer -> Maybe Integer
addToMaybe Nothing _ = Nothing
addToMaybe (Just a) b = Just (a + b)
```

Nothingや(Just a)によってMaybe値をパターンマッチしている。パターンマッチはうえから順に試されていく。マッチしたパターンに含まれる変数が、なかみの値に束縛される。試してみる。

```
> :reload
> addToMaybe (safeDiv 35 4) 7
Just 15
```

リスト
-----

おなじ型の値がたくさん必要なとき、リストを使う。

```
> [123, 456, 789]
[123,456,789]
```

リストを操作するにはパターンマッチと再帰を使う。

```
% vim samples/list.hs
```

```hs:samples/list.hs
sumAll :: [Integer] -> Integer
sumAll [] = 0
sumAll (n : ns) = n + sumAll ns
```

リストは(n : ns)のようなパターンで、(先頭の値 : 残りの値)のようにわけられる。要素をもたないリストは[]で表される。要素をもたないリストの総和は0。先頭がnで残りがnsであるリストの総和は、値nにリストnsの総和をたしたもの。試してみる。

```
> :load samples/list.hs
> sumAll [5, 7, 9, 2]
23
> sumAll [123, 456, 789]
1368
> sumAll [2, 4, 5]
11
```

定義を再掲する。

```
sumAll [] = 0
sumAll (n : ns) = n + sumAll ns
```

展開してみよう。

```
sumAll [2, 4, 5]
=> 2 + sumAll [4, 5]
=> 2 + (4 + sumAll [5])
=> 2 + (4 + (5 + sumAll []))
=> 2 + (4 + (5 + 0))
=> 11
```

文字列
-----

Haskellでは、デフォルトの文字列は文字のリストだ。文字列は、つぎのように表記される。

```
"foobar"
```

これは、つぎのリストとおなじ。

```
['f', 'o', 'o', 'b', 'a', 'r']
```

たしかめてみよう。

```
> "foobar"
"foobar"
> ['f', 'o', 'o', 'b', 'a', 'r']
"foobar"
```

case式
------

パターンマッチを関数定義ではなく、式のなかに書くにはcase式を使う。

```
% vim samples/case.hs
```

```hs:samples/case.hs
import Data.Char

checkAnswer :: Char -> Maybe Bool
checkAnswer c = case toLower c of
        'y' -> Just True
        'n' -> Just False
        _ -> Nothing
```

文字cをtoLowerで小文字化したものに対して、パターンマッチをおこなっている。試してみる。

```
> :load samples/case.hs
> checkAnswer 'y'
Just True
> checkAnswer 'N'
Just False
> checkAnswer 'p'
Nothing
```

タプル、Maybe値、リストのまとめ
--------------------------

Haskellで用意されているデータ構造を紹介した。

* タプル
* Maybe値
* リスト

なかみを取り出すにはパターンマッチを使う。リストのなかみをすべて利用するには、再帰関数を利用する。パターンマッチを「式のなか」でするには、case式を使う。

[その2へ](https://qiita.com/YoshikuniJujo/items/ad584549cf75c5d28745)