【じゃんけんゲームで入門】Haskell

当記事では、自作したじゃんけんゲームを題材にしてHaskellの文法や使用するときの注意点などを確認していきます。Windows 10 Home 64bit版環境で検証しています。

ソースコードは以下よりご確認ください。

[rps-like/Haskell at master · tomomoss/rps-like](https://github.com/tomomoss/rps-like/tree/master/Haskell)

## じゃんけんゲームとは

じゃんけんゲームはコンソール上で動作するCUIゲームです。

基本的なルールは既存のじゃんけんそのものです。じゃんけんで勝つごとに1点取得し、対戦相手よりも先に5点先取することで勝者となります。

対戦内容はログファイルに書き出して、後から読み返せるようにします。

## Haskellとは

Haskellは1990年に発表された関数型言語です。関数型言語にはいくつかのプログラミング言語が該当しますが、Haskellはそれらのなかでも特に厳格な言語仕様が敷かれており、そのことから **純粋関数言語** と評されます。

今回書いてみた感想ですが、ハッキリ言って非常に難解でした。主流となっている手続き型言語・オブジェクト指向言語とは言語の根底に流れるパラダイムが異なりますので、手続き型言語やオブジェクト指向言語での経験・技術・考え方がHaskellの習得にあまり役に立ちませんでした。「全く違う言語だった」とまで言ってしまうと、それは言い過ぎですが思っている以上に違っていたためにかなり戸惑いました。

とはいえ、いや、むしろだからこそ今回の試みは貴重な体験になりました。極端な話――10個の手続き型言語やオブジェクト指向言語を学ぶよりも、それら言語とHaskellを始めとする関数型言語を1つずつ学んだほうが意味があるのではないかと思えるほどに、鮮烈で特異な言語でした。

Haskellを学んだところで実務に直接役立つということはあまりないでしょうが、ITエンジニアとしての視野を広げる意味でも一度くらいは触っておいて損はないと思います。

## 開発環境構築手順

Haskellコンパイラの1つ「GHC（The Glasgow Haskell Compiler）」の導入方法を解説します。

### コンパイラをダウンロード

GHCの公式サイトからコンパイラをダウンロードします。以下ダウンロードページに移動してください。

[Download — The Glasgow Haskell Compiler](https://www.haskell.org/ghc/download.html)

歴代バージョンが並んでいますので、お好きなバージョンを選択してください。ちなみに、当記事執筆時点ではv9.0.1が最新でしたのでv9.0.1をダウンロードすることにします。

[GHC 9.0.1 download — The Glasgow Haskell Compiler](https://qiita.com/tomomoss/items/84ec5cba062ea16f753f)

バージョン番号をクリックすると詳細ページに飛んだかと思います。詳細ページ下部には各OS向けのバイナリが用意されていますので、ご自身の環境に合わせたものをダウンロードしてください。今回はWindows 64bit版向けのバイナリを選びます。

### 解凍と配置

コンパイラが入っている圧縮ファイルはWindowsでおなじみのzip形式ではなく、tar形式とxz形式で圧縮されています。そのためWindowsの標準アーカイバでは解凍できません。

個人的には「7-zip」というアーカイバが良い感じです。

[圧縮・解凍ソフト 7-Zip](https://sevenzip.osdn.jp/)


なお、少なくない数のWindowsユーザーに愛されているであろう「Lhaplus」はxz形式に非対応ですのでご注意ください。

解凍できましたら格納されているディレクトリ（おそらく「ghc-9.0.1-x86_64-unknown-mingw32」という名前だと思います）を適当な場所に置いてください。私はドライブ直下に置くようにしています。

### PATHを通す

当該ディレクトリ直下の「bin」ディレクトリにPATHを通しておいてください。

### 動作確認

最後に動作確認をしておきましょう。以下コマンドを叩いてGHCのヘルプが表示されたら問題ありません。

```
PS C:\Users\tomomoss> ghc --help
```

## 注意したい仕様

基本的な文法などはGitHubに上げているソースコードを読んでいただくか、あるいは自前で調べていただくとして――ここからは、GitHubに上げているソースコードからは読み取れない仕様や初めてHaskellを触る人に向けての注意事項を列挙します。

### Haskellの動かし方

ソースファイルの拡張子は「.hs」です。

エントリポイントとなるのはmainという識別子の関数です。なお、以下コードを見てもらえれば分かるように、Haskellはインデントによってブロックを表す **オフサイドルールを採用している** ことに留意してください。

```haskell
main = do
    -- ※色々な処理
```

ソースファイルは以下のようにコンパイルします。

```
PS C:\Users\tomomoss> ghc .\example.hs
```

### コメント

コメントは2種類あります。

1行コメントはハイフン記号を2つ重ねます。

```haskell
-- ここがコメントです。
```

複数行コメント（ブロックコメント）は波括弧の内側にハイフン記号を付けます。

```haskell
{-
    ここがコメントです。
-}
```

### 文字と文字列

Haskellでは **文字と文字列は異なる** ものとして解釈されます。

文字はシングルクォート記号で囲んだ1文字です。

文字列はダブルクォート記号で囲んだ1文字以上の文字群です。また、文字列は厳密には文字型のリストであり、ダブルクォート記号を使った表記は糖衣構文です。

```haskell
-- ダブルクォート記号を使った糖衣構文です。
"abc"

-- 上記値と以下値は同義です。
['a', 'b', 'c']
```

### リストとタプル

リストは複数の値をまとめたものです。値は角括弧で囲み、各値はカンマ記号で区切ります。あるリストに格納される値は全て同一のものでなければなりません。

```haskell
[1, 2, 3]
['a', 'b', 'c']
```

タプルも複数の値をまとめたものですが、格納される値のデータ型は1種類である必要はありません。その代わりに、格納される値の数は事前に決めておく必要があります。値は丸括弧で囲み、各値はカンマ記号で区切ります。

```haskell
(1, 'a', 2, 'b')
```

### 関数の定義

Haskellでは関数が処理の基本単位になっています。関数は次のように記述します。

```haskell
関数名 引数1 引数2 引数n = 戻り値、あるいは戻り値となる式
```

**関数名は大文字から始まってはいけません** し、 **必ず何らかの値を返さなければいけません** 。

戻り値や計算式が1行で書ききれないという場合はdo式を使用することで解決できます。このときはオフサイドルールに気を付けてください。

```haskell
関数名 引数1 引数2 引数n = do
    戻り値、あるいは戻り値となる式
```

また、関数を定義するときは引数と戻り値のデータ型を指定することが推奨されています。関数定義の直前に「関数名 :: 引数のデータ型 -> 戻り値のデータ型」という風に記述します。引数が増えるごとに「->」で区切っていきます。

```haskell
{-
  引数はなく、
  戻り値のデータ型が「A」である関数です。
-}
example1 :: A
example1 = do
  -- ※色々な処理処理

{-
  第1引数のデータ型が「A」で、
  戻り値のデータ型が「B」である関数です。
-}
example2 :: A -> B
example2 foo1 = do
  -- ※色々な処理処理

{-
  第1引数のデータ型が「A」で、
  第1引数のデータ型が「B」で、
  戻り値のデータ型が「C」である関数です。
-}
example3 :: A -> B -> C
example3 foo1 foo2 = do
  -- ※色々な処理処理
```

様々なデータ型の値を取り扱いたい場合は **型変数** を利用します。型変数とは小文字で始まるデータ型です。

```haskell
{-
  あるデータ型「a」を引数に取り、
  引数と同じデータ型「a」の値を返す関数です。
-}
example :: a -> a
example foo = do
  -- ※色々な処理処理
```

### ガード

ガードは他のプログラミング言語でいうところのselect文やswitch文に相当する機能です。個人的に、Haskellに採用された機能のなかでも最も素晴らしい機能の1つであると考えています。

```haskell
-- 引数に応じた値を返します。
example :: Int -> String
example foo
  | foo < 0 = "引数は負の値です。"
  | foo > 0 = "引数は正の値です。"
  | otherwise = "引数は0です。"
```

### return

Haskellの「return」は特定の値を返すのではありません。returnが返すのは **アクション** です。

「otherwise」とは全てのパターンマッチに該当しないときに分岐する処理です。