はじめの一歩
1. 式の評価
早速 SML/NJ を立ち上げてみましょう.
端末で sml
と打てば起動できます.
% sml
Standard ML of New Jersey v110.76 [built: Tue Oct 22 14:04:11 2013]
この対話環境を閉じるには,UNIX であれば ^D を入力します. また,対話環境で実行中の処理を中断したいときには,UNIX であれば ^C を入力します.
簡単な式を評価してみましょう.
- 2 + 3;
val it = 5 : int
対話環境では,プログラムの区切りをセミコロン ;
で教えることにより,式を評価できます.
セミコロンを打つまでは,複数行にわたって式を書くこともできます.
- 2
= +
= 3
= ;
val it = 5 : int
(この出力結果の2行目から4行目までの ``=`` は SML/NJ が勝手に出力したプロンプトです. 今後は見やすさのために省略します.)
式 2 + 3
を評価すると,val it = 5 : int
という出力が得られます.
これは,式 2 + 3
が int
型の値 5
に評価されたことを意味しています.
式の評価結果は一時的に it
という仮の変数に割り当てられます.
他にもいろいろな式を試してみましょう.
- 2;
val it = 2 : int
- it + 3;
val it = 5 : int
- 1 + 2 * 3;
val it = 7 : int
- 3.14;
val it = 3.14 : real
- not true;
val it = false : bool
- "hello";
val it = "hello" : string
- [1, 2, 3];
val it = [1,2,3] : int list
2. 変数
既に it
という名前の変数が登場しましたが,ここでは式の値に自分の好きな名前をつけてみましょう.
- val num : int = 2 + 3;
val num = 5 : int
- 2 * num;
val it = 10 : int
あるいは,型付けを型推論器に任せて,以下のように書くこともできます.
- val num = 2 + 3;
val num = 5 : int
- 2 * num;
val it = 10 : int
ここでは,5
という値に num
という名前が付けられています.
手続き型の世界ではこの状況を "変数 num
に値 5
を代入する" と表現しますが,関数型の世界では "変数 num
を値 5
に束縛(bind)する" といいます.
関数型の世界には "代入" という概念は存在しません(後述する参照型を除く). 手続き型の世界の変数は代入によって値が変化しますが,関数型の世界の変数は値が更新されることはありません.
以下の例を見るといかにも値の更新が行われているかのように見えますが,これは単に変数 a
の定義が上書きされているだけです.
(別の言い方をすれば,既に束縛されている変数の定義はこのように上書きすることが可能です.)
- val a = 123;
val a = 123 : int
- val a = "abc";
val a = "abc" : string
3. 識別子
識別子には "英数字名" と "記号名" が使えます.
-
英数字名
1字目が英字 (A-Z, a-z) またはアポストロフィ (') で,2字目以降が英字 (A-Z, a-z),数字 (0-9),アポストロフィ ('),アンダースコア (_) である文字列. (ただし,アポストロフィで始まる英数字名は型変数にのみ使われる.) -
記号名
以下に示す記号からなる文字列.
! % & $ # + - / : < = > ? @ \ ~ ` ^ | *
例えば,以下のようにへんてこな名前の変数を定義することもできます.
- val !? = 1;
val !? = 1 : int
予約語は以下の通りです.
abstype and andalso as case do
datatype else end exception fn fun
handle if in infix infixr let
local nonfix of op open orelse
raise rec then type val with
withtype while eqtype functor include sharing
sig signature struct structure where
( ) [ ] { } , : ; ... _ | = => -> # :>
4. 関数
簡単な関数を定義してみましょう.
- fun add (x : int)(y : int): int = x + y;
val add = fn : int -> int -> int
- add 1 2;
val it = 3 : int
ここでも,型は以下のようにすべて省略してしまうことができます.
- fun add x y = x + y;
val add = fn : int -> int -> int
- add 1 2;
val it = 3 : int
1行目のキーワード fun
で始まる宣言が,関数の宣言になっています.
ここでは,2つの引数 x
, y
をとり x + y
の値を返す関数 add
を定義しています.
関数の型は int -> int -> int
のように,->
を使って表現されます.
->
の左は引数の型,右は返り値の型です.
int -> int -> int
の正しい読み方については後ほど説明します.
4行目 add 1 2
では,値 1
と 2
に関数を適用(apply)しています.
手続き型の世界での "呼び出し" という言葉の代わりに,関数型の世界では "適用" という言葉を使います.
関数適用の構文も C 言語族の構文とはずいぶん趣が異なります. ML では,関数名に続いて引数を空白で区切って並べることにより,関数適用を記述します. ラムダ計算の記法の影響を受けていますね.
ML では演算子が最も優先順位が低いことに注意しましょう.
例えば,add (1 * 2) (3 * 4)
は括弧を外すことができません.
また,add 1 2 * add 3 4
は (add 1 2) * (add 3 4)
のように結合します.
- add (1 * 2) (3 * 4);
val it = 14 : int
- add 1 2 * add 3 4;
val it = 21 : int
5. コメント
コメントは (*
から *)
までの間に記述します.
ネスト可能です.
ちなみに,いわゆる 1 行コメントは利用できません.
(* コメントは (* ネスト可能 *) *)
6. ファイルの使用
プログラムをファイルに記述して,対話環境にロードすることもできます.
例として,以下のような test1.sml
というファイルを作成してみましょう.
(* file: test1.sml *)
fun add x y = x + y
val m = 2
val n = 3
キーワード fun
や val
の存在でプログラムの切れ目がわかるので,対話環境では常に入力していたセミコロン (;
) は必ずしもつける必要はありません.
ファイルの内容をロードするには,以下のように use
関数を使います.
- use "test1.sml";
[opening test1.sml]
val add = fn : int -> int -> int
val m = 2 : int
val n = 3 : int
val it = () : unit
- add 4 5;
val it = 9 : int