Standard MLで関数型プログラミングを体験しよう

# Standard MLとは？
Standard ML（以下、SML）とは、プログラミング言語の一種です。関数型言語に分類されており、高階関数やパターンマッチングなどといった言語要素がサポートされています。

:::note info
高階関数とは、引数や返り値として関数を扱う関数。高階関数は、関数を整数や文字と同様に値として扱うことができる。
:::

:::note info
パターンマッチングとは、扱うデータをパターンを用いて記述することにより、複雑な条件分岐やデータの抽出などの記述を簡略化できるしくみ。
:::

# インストール
本記事では、コンパイラはStandard ML of New Jerseyを使用します。以下のサイトからダウンロードできます。

https://www.smlnj.org/

Linuxを使用している場合は、aptコマンド等でインストールすることもできます。以下はUbuntu20.04でのインストールの例です。

```
sudo apt install smlnj
```

他にもmltonや、日本製のSML#などといったコンパイラもありますので、興味のある方は調べてみてください。

# サンプルプログラムの実行
プログラミングでおなじみの、Hello World!と表示するプログラムを書いてみましょう。

## インタプリタによる実行
正常にSMLがインストールされていれば、Windowsではコマンドプロンプトやパワーシェル、Linuxではbash等で`sml`コマンドを実行することでインタプリタを起動できます。

```
$ sml
Standard ML of New Jersey v110.79 [built: Sat Oct 26 12:27:04 2019]
-
```

では`Hello World!`と表示してみましょう。標準出力への出力は`print`関数を用います。

```
- val x = print "Hello World!\n";
Hello World!
val x = () : unit
```

print関数で文字列を出力することができました。
SMLでは、`val (変数名) = (式)`で変数を宣言することができます。ここでは、変数xにprint関数の返り値が格納されます。
また、SMLでは、`(関数) (引数)`の形で関数に引数を渡すことができます。この例では、print関数に文字列`"Hello World!\n"`を渡しています。`\n`は改行を表します。インタプリタを使用する際は、文の最後に`;`をつけます。

## ファイルを読み込んで実行
次に、あらかじめファイルにプログラムを記述して、インタプリタからファイルを読み込んでみましょう。以下のようなプログラムを作成します。ファイルにプログラムを記述する場合は、文の最後の`;`は無くても大丈夫です。

```hello.sml
(* hello.sml *)
val x = print "Hello World!\n"
```

プログラムが準備できたら、`sml`コマンドでインタプリタを起動し、`use (ファイル名へのパス);`でプログラムを指定して実行してみます。

```
- use "hello.sml";
[opening hello.sml]
Hello World!
val x = () : unit
val it = () : unit
```

# 各種文法事項
簡単に各文法事項を説明します。

```
(* 変数の宣言 *)
val x = 1
val str_one = "one"

(* 関数の宣言 *)
fun f x = x + 1

(* if式 *)
val y = if f 5 > 10 then "more than ten" else "ten or less"

(* 再帰呼び出し *)
fun sum n = if n <= 0 then 0 else n + sum (n - 1)
```

変数の宣言は`val (変数名) = (式)`
関数の宣言は`fun (関数名) (引数) [(引数)...] = (式)`
if式は`if (条件) then (条件が真のときの式) else (条件が偽のときの式)`
というように書きます。上の例では、関数fに5を渡した返り値は6となります。6は10より小さいので、`f 5 > 10`の結果はfalseになり、elseへ分岐します。よって、変数yには文字列`"ten or less"`が代入されます。
関数の中で、自分自身を呼び出す再帰呼び出しを行うこともできます。基本的に、関数型言語ではforやwhileといった言語要素を使用せず、再帰呼び出しで繰り返し処理を行います。

# FizzBuzz問題
最後に、FizzBuzz問題を解いてみましょう。FizzBuzz問題とは、
「1から順に整数を出力しなさい。ただし、3の倍数のときにはFizz、5の倍数のときにはBuzz、3と5の倍数のときにはFizzBuzzを出力せよ。」
という問題です。ここでは30まで出力してみます。

## 解答
2つの関数を作りましょう。まず、1以上の整数nを受け取り、出力する内容を文字列として返す関数f1を次のように定義します。複数の条件が当てはまる場合には、上側の場合を優先します。例えば、引数が15のとき、15は15で割り切れますし、3でも5でも割り切れますが、一番上のFizzBuzzを出力します。

```math
f1(x) = \left\{
\begin{array}{ll}
FizzBuzz & (xが15で割り切れるとき) \\
Fizz & (xが3で割り切れるとき) \\
Buzz & (xが5で割り切れるとき) \\
x & (その他)
\end{array}
\right.
```

これをプログラムにすると、次のようになります。Int.toString関数は、整数型の値を受け取って、対応するstring型の値を返す関数です。

```
fun f1 x =
  if x mod 15 = 0 then "FizzBuzz"
  else if x mod 3 = 0 then "Fizz"
  else if x mod 5 = 0 then "Buzz"
  else Int.toString x
```

次に、1から30までの出力リストを返す関数f2を定義します。f2はプログラム中で引数1を渡すものとします。SMLでは、`(式1; 式2; 式3;... 式n)`と書かれた場合、式1から式nまで順に評価を行い、式nをこの式全体の評価結果とします。

```
fun f2 x =
  if x <= 30
  then (print (f1 x ^ "\n"); f2 (x + 1))
  else ()
```

最後に、関数f2に1を渡せば、コンソール上に答えが表示されます。ちなみに、変数名をアンダースコアにすると、その値は破棄されます。

```
val _ = f2 1
```

以上でプログラムの作成が完了しました。プログラム全体は以下の通りです。「出力を分ける部分」「30まで出力を行う部分」が分かれていて見やすいですね。

```fizzbuzz.sml
fun f1 x =
  if x mod 15 = 0 then "FizzBuzz"
  else if x mod 3 = 0 then "Fizz"
  else if x mod 5 = 0 then "Buzz"
  else Int.toString x

fun f2 x =
  if x <= 30
  then (print (f1 x ^ "\n"); f2 (x + 1))
  else ()

val _ = f2 1
```

インタプリタ上で実行すると次のようになります。

```
- use "fizzbuzz.sml";
[opening fizzbuzz.sml]
[autoloading]
[library $SMLNJ-BASIS/basis.cm is stable]
[library $SMLNJ-BASIS/(basis.cm):basis-common.cm is stable]
[autoloading done]
1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
11
Fizz
13
14
FizzBuzz
16
17
Fizz
19
Buzz
Fizz
22
23
Fizz
Buzz
26
Fizz
28
29
FizzBuzz
val f1 = fn : int -> string
val f2 = fn : int -> unit
val it = () : unit
```

## 別解
ここでは説明していませんが、map関数やapp関数、パターンマッチなどを用いると、次のように書くこともできます。

```
val range = List.tabulate (30, (fn x => x + 1))
val changeList = [(15, "FizzBuzz"), (3, "Fizz"), (5, "Buzz")]
fun change ((n, s)::t) x = if x mod n = 0 then s else change t x
  | change nil x = Int.toString x
val _ = app (fn s => print (s ^ "\n")) (map (change changeList) range)
```