Haskell入門ハンズオン! #3: 当日用資料 1/5

Haskell入門ハンズオン! #3: 当日用資料 1/5
======================================

はじめに
-------

この記事は2018年3月11日に開催の[Haskell入門ハンズオン #3](
https://shinjukuhs.connpass.com/event/77801/)の当日用の資料だ。

サンプルコード
------------

サンプルコードは以下の短縮URLから入手できる。

https://goo.gl/GQdqsT

内容
----

Haskellでパーサコンビネータを自作し、四則演算の式を解析して(しつつ)、評価するプログラムを作っていく。コードを書きながら、Haskellの構文や技法を紹介していく。例題のコードは拙書「[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)」の例を使った。

「第20章 演習: 構文解析」の1章ぶんをまるまる解説する。第20章はこの書籍の298から310ページまでの内容だ。書籍では200ページ以上かけて、この例題で必要な要素を説明している。その部分をまるまるとばす。コードを書くなかで、適宜、解説をいれるが、完全に理解できなくていい。雰囲気を感じとってもらいたい。


環境構築
-------

環境の構築は[Haskell入門ハンズオン! 事前資料(2/3)](https://qiita.com/YoshikuniJujo/items/088a7caea9bd3fe59e3a)を参考にしてもらいたい。

環境の確認
--------

つぎのようにして、環境構築ができていることを確認する。

    % stack ghc -- --version
    The Glorious Glasgow Haskell Compilation System, version 8.2.2

対話環境
------

対話環境を試してみよう。ターミナルにstack ghciと打ち込む。

    % stack ghci

とりあえず、数値や文字を打ち込む。

```hs
Prelude> 123
123
Prelude> 'c'
'c'
```

足し算や引き算もできる。

```hs
Prelude> 3 + 5
8
Prelude> 15 - 9
6
```

関数を使う
--------

関数を使ってみよう。

```hs
Prelude> negate 8
-8
```

関数を使うには、つぎのようにする。

    [関数名] [引数1] [引数2]

+や-などの演算子も、実は関数だ。

```hs
Prelude> (+) 3 5
8
Prelude> (-) 15 9
6
```

関数(+)に引数3と5とを、関数(-)に引数15と9とを、それぞれ、あたえている。

関数を作る
--------

まずは、変数を定義してみる。

```hs
Prelude> n = 1234
Prelude> n
1234
Prelude> n + 4321
5555
```

関数を定義する。半径から円の面積をもとめる関数。

```hs
Prelude> area r = r ^ 2 * pi
Prelude> area 3
28.274333882308138
```

半径3cmの円の面積は、だいたい28cm^2。変数の定義と関数の定義の構文は、よく似ている。Haskellでは変数piは円周率として定義ずみだ。

定義ファイル
----------

対話環境を終了する。

```hs
Prelude> :quit
```

対話環境で定義した関数は、対話環境の終了とともに消えてしまう。定義した関数を、もっと長く使いたいときには、関数を定義したファイルを対話環境に読み込んで使う。好きなエディタでファイルcircle.hsを作る。僕の場合はVimを使う。

```hs:circle.hs
area r = r ^ 2 * pi
```

これを対話環境に読み込む。

    % stack ghci
    Prelude> :load circle.hs

試してみよう。

```hs
*Main> area 3
28.274333882308138
```

読み込めたようだ。ここで、対話環境は立ち上げたままにして、ファイルcircle.hsに円周をもとめる関数を追加する。

```hs:circle.hs
circumf r = r * 2 * pi
```

対話環境に、コマンド:reloadを打ち込む。

```hs
*Main> :reload
*Main> circumf 3
18.84955592153876
```

たいていにおいて、僕の開発スタイルはターミナルを、ふたつに分割して、Vimと対話環境を開きっぱなしにして、ファイルを変更するたびに:reloadしながら。というもの。

型
--

値には型がある。対話環境で型をみてみよう。

```hs
*Main> :type 'c'
'c' :: Char
```

文字'c'はChar型の値だ。

```hs
*Main> :type True
True :: Bool
```

真(本当)であることをあらわす値TrueはBool型の値だ。

### 関数の型

関数にも型がある。関数の型は、引数の型と返り値の型とで決まる。例として、文字を大文字にする関数と、文字が大文字であることを確認する関数をみる。まずは、それぞれを使ってみる。

```hs
*Main> :module Data.Char
Prelude Data.Char> toUpper 'c'
'C'
Prelude Data.Char> isUpper 'c'
False
```

関数toUpper, isUpperはモジュールData.Charから公開されている。対話環境では、コマンド:moduleでモジュールを導入する。

関数toUpperは引数も返り値もChar型の値であり、関数isUpperは引数がChar型で、返り値はBool型の値だ。それぞれの型をみてみよう。

```hs
Prelude Data.Char> :type toUpper
toUpper :: Char -> Char
Prelude Data.Char> :type isUpper
isUpper :: Char -> Bool
```

関数の型は、引数の型と返り値の型とを'->'でつなぐ。

### 型宣言

Haskellでは(たいてい)型は自動的に決まる。しかし、型を明示するのは良い習慣だ。ファイルCircle.hsを編集する。

```hs:circle.hs
area :: Double -> Double
area r = r ^ 2 * pi

circumf :: Double -> Double
circumf r = r * 2 * pi
```

対話環境に読み込んで試す。

```hs
Prelude Data.Char> :load circle.hs
*Main> area 3
28.274333882308138
```

入出力について
-----------

Haskellでは「関数は関数」だ。「足し算を評価するついでに、設定ファイルを書きかえておきましたよ」といった(気の利いた|余計な)ことはしてくれない。関数は入出力をしない。

...

「入出力なしで、どうやって生きていけと?!」
Haskellでは関数の評価と入出力とは分離されている。
入出力は、むしろ、ただの値だ。

リストという構造がある。たとえば数のリストの総和をもとめられる。

```hs
*Main> sum [3, 4, 5]
12
```

入出力も値なのでリストの要素にすることができる。

```hs
*Main> hello = [putStrLn "hello", putStrLn "world"]
```

リストに含まれる入出力を組み立てることもできる。

```hs
*Main> sequence_ hello
hello
world
```

逆順にして組み立てることもできる。

```hs
*Main> sequence_ (reverse hello)
world
hello
```

対話環境は評価した結果の値が入出力だったとき、その入出力を実行してくれる。ちなみに、結果の値が入出力でないとき、もしそれが表示可能なら、表示する。

Haskellでは入出力(および状態変化)は、IOモナドという、わくぐみであつかわれる。今回はIOモナドについては、くわしく説明しない。とりあえず覚えておくことは、Haskellでは「入出力は評価のついでに、おこなうものではない」「入出力も、また値である」ということだ。
詳細は拙書を参照のこと。

[当日用資料 2/5へ](https://qiita.com/YoshikuniJujo/items/f63c1be3ff07c3048aba)