1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

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

Posted at

Standard MLとは?

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

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

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

インストール

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

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を出力します。

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)
1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?