プログラミングはプログラムを作成する行為です。本章ではプログラミングの基本を学習する前に、プログラムとは具体的に何を指すのか、そしてプログラミングを行う上で利用する型や型システムについて整理します。
加えて、学習を進めるために有用なOCamlのツール群のインストール方法についても説明します。
プログラムまたはコンピュータプログラム(以下、プログラム)の定義は、文脈や時代によって様々な表現があります。例えば「Principles of Information」という書籍では、「コンピュータを操作するためのもの」と説明されています。人がプログラムを書くためにはプログラミング言語が利用されます。プログラミング言語を使用して書かれたプログラムは一般的にソースコードと呼ばれます。ソースコードには、コンピュータを操作したり値を生成したりするための式や文が記述されています。言い換えれば、プログラムは式と文を組み合わせて何らかの値 (value)を算出する仕組みと考えることができます。
式から値を得るための計算過程を評価 (evaluation)と言います。いくつかのプログラミング言語およびOCamlには型 (type)という概念があり、これは値がどのような種類のデータに分類されるかを示すものです。
型は意味のない計算を防ぐ仕組みであり、例えば「1 + "ほげ"」のように数値と文字列を可算するといった無意味な計算を未然に防ぎます。このようにデータの種類を確認することを型チェック (type check)と言います。プログラミングにおいて無意味な計算によって発生するエラーを型エラー (type error)と言います1。
また、型チェックによってエラーを防ぐ目的を持つ仕組みを型システム (type system)と言います。なお、型、型チェック、型システム等の定義はプログラミング言語によって異なることに留意してください。例えば「型システム」について、書籍「型システム入門」では十分に意味のある定義を与えることが難しいことを指摘しつつ、次のような定義を示しています。
型システムとは、プログラムの各部分を、それが計算する値の種類に沿って分類することにより、プログラムがある種の振る舞いを起こさないことを保証する、計算量的に扱いやすい構文的手法である。
OCamlは型システムを持つためプログラム実行の前にこのような無意味な計算を発見でき、一度チェックが完了すると型の不一致が発生しない性質が保証されています。このような性質を型安全性 (type safety)と言います2。
このような理由から、OCamlのように型や型システムを持つプログラミング言語を学習することは有用です。
本書ではOCamlのコード例を用いて説明します。OCamlのコードを手元で確かめるにはocamlコマンドまたはutopコマンドが必要です。本章ではUbuntu OSにおけるインストール方法を紹介します。
Ubuntuにログインしターミナル(端末)を起動します。始めに、下記コマンドを実行しパッケージをアップデートします。
% sudo apt update
必要に応じてパッケージのアップグレードも行います。
% sudo apt upgrade -y
次にopamというOCamlのパッケージマネージャをインストールします。これをインストールするとocamlコマンドもインストールされます。
% sudo apt install opam -y
opamをインストール後、下記コマンドを実行しopamの初期化を行います。
% opam init --bare -a -y
OCamlにはutopというREPL(Read-Eval-Print Loop)というツールが存在します。REPLとは実行環境の1つであり、プログラマが入力した文字列を受け取ってそれを解釈および実行し結果を返すプログラムです。このようなプログラムは対話型トップレベル (Interactive toplevel)とも呼ばれます。utopは結果を返した後で再び入力を待ち受けます。utopは補完機能等を備えているため、本書ではutopの利用を推奨します。utopをインストールするには、以下のコマンドを実行します。
% opam install utop
コンソールにutopと入力し、utopを起動します。
% utop
終了するにはCtrl + dを入力します。
この章では、プログラミングの基本となるデータとその型を説明します。本章を読み進める際には、初学者の方はお手元にインタプリタ(例:utopコマンドやocamlコマンド等)を起動して、確かめながら読み進めてみてください。
整数とは0とそれに1ずつ加えて得られる数(自然数、例えば1、2、3...)および1ずつ引いて得られる数(例えば、-1、-2、-3...)の総称です。OCamlにおいて整数はintという型で表現されます。それではutopで整数をインタプリタに与えた例をみてみます。
utop # 2 ;;
- : int = 2
例えば1行目で2 ;;と入力した時の結果が2行目に表示されており、2行目は「直前(-)に入力した結果の型はintであり、結果の値は2である」ことを表しています1。
int型には算術演算として(紙面上で計算する時に用いる時と同じように)次の四則演算があります。可算を表す+、減算を表す-、乗算を表す*、除算の商を表す/、余りを表すmodがあります。
utop # 4 + 3 ;;
- : int = 7
utop # 5 - 3 ;;
- : int = 2
utop # 6 * 3 ;;
- : int = 18
utop # 7 / 3 ;;
- : int = 2
utop # 8 mod 3 ;;
- : int = 2
このように演算に用いる記号を演算子 (Operator)と言います。特に四則演算に用いる演算子は四則演算子とも呼ばれます。
実数とは連続した量を表すための数であり、イメージとしては数直線上に並べることができる全ての数を言います。実数は円周率(3.14...)等の小数点表現や指数表現はもちろん、前節で説明した整数も含まれます。このように実数には、整数のようにa/b(a、bともに0でない)の形で表現できる有理数と、円周率(3.14...)のようにa/b(a、bともに0でない)で表現できない無理数があります。
コンピュータでは実数を正確に表現できないため、実数の近似値を表現するための浮動小数点数 (floating-point number)という方式を用います。OCamlでは浮動小数点数をfloatという型で表現します。utopで浮動小数点数をインタプリタに与えた例をみてみます。
utop # 3.14 ;;
- : float = 3.14
float型にもint型同様に四則演算が定義されています。注意点として、OCamlはint型とfloat型を明確に区別しています。そのため、int型のための四則演算である+等をfloat型に対して利用できず、float型用の四則演算を利用する必要があります。float型の可算を表す+.、減算を表す-.、乗算を表す*.、除算の商を表す/.があります2。
float型の演算の例を以下に示します。
utop # 3.14 +. 4.0 *. 5.0 /. 3.0 ;;
- : float = 9.80666666666666664
また、小数点以下が0の場合は、その0を省略することができます。次の例は前述の式と同じ式になります。
utop # 3.14 +. 4. *. 5. /. 3. ;;
- : float = 9.80666666666666664
加えて、float型にはべき乗を求める関数**も定義されています。なお**にはドット(.)が付かないことに注意してください。
utop # 3. ** 3. ;;
- : float = 27.
ここで、float型の四則演算をint型に適用した場合またはその逆(int型の四則演算をfloat型に適用した場合)について説明します。float型の四則演算ではfloat型の値のみ受け取ることができ、int型の四則演算はint型の値のみを受け取ることができます。四則演算に渡される型が1つでも合わない場合はエラーとなります。
utop # 3 ** 3. ;;
Line 1, characters 0-1:
Error: This expression has type int
but an expression was expected of type float
Hint: Did you mean `3.'?
上記はint型の3にfloat型の演算子である**を適用してしまった例です。インタプリタが出力した1行目にはエラーとなった箇所が示されており、「与えられた入力の1行目の0から1文字目でエラーが発生しました」という内容になります。また、2行目にはエラーメッセージが示され、内容は「この式はint型を受け取ったが、式はfloat型を期待していました」となります。さらに3行目にはエラー箇所に関するヒントとして「3.ではないですか?」という内容が示されています。
文字は英数字1文字を表すもので、OCamlは8bits(1byte)で表現される1文字を表します3。文字を扱いたい場合は引用符'...'で囲みます。OCamlにおいて文字はcharという型で表現されます。utopで文字をインタプリタに与えた例をみてみます。
utop # 'a' ;;
- : char = 'a'
なお、文字は1文字の表現であるため'ab'のように2文字をシングルクォート(')で囲むことはできません。加えて、空文字('')を書くこともできません。
utop # 'ab' ;;
Error: Syntax error
utop # '' ;;
Error: Syntax error
OCamlにおいて文字列は単なる文字のリスト(文字を連結したもの)ではありません。そのため文字とは異なるデータであり、文字列を扱いたい場合は引用符"..."で囲みます。OCamlにおいて、文字列はstringという型で表現されます。utopで文字列をインタプリタに与えた例は以下のとおりです。
utop # "abc" ;;
- : string = "abc"
文字とは異なり、文字列は1文字であってもダブルクォート(")で囲まれていれば、正しく文字列として評価されます。また、空の文字列("")も書くことができます。
utop # "a" ;;
- : string = "a"
utop # "" ;;
- : string = ""
さらに、文字列同士を結合する演算子である^が定義されています。
utop # "a" ^ "bc" ^ "def" ;;
- : string = "abcdef"
真偽値は真または偽を表す値です。真はtrueで表し、偽はfalseで表します。OCamlにおいて、真偽値はboolという型で表現されます。真偽値をインタプリタに与えた例をみてみます。
utop # true ;;
- : bool = true
utop # false ;;
- : bool = false
整数や浮動小数点数では四則演算が定義されていましたが、真偽値には論理演算が定義されています。OCamlには、かつを表す&&、またはを表す||、否定を表すnotがあります。
utop # true && true ;;
- : bool = true
utop # true && false ;;
- : bool = false
utop # false && false ;;
- : bool = false
utop # true || true ;;
- : bool = true
utop # true || false ;;
- : bool = true
utop # false || false ;;
- : bool = false
utop # not true ;;
- : bool = false
utop # not false ;;
- : bool = true
また四則演算では加減算よりも乗除算の方が優先度が高い(つまり乗除算を先に計算する)というルールがありますが、真偽値においては優先度の高い順から「否定>かつ>または」というルールがあります。そのため、次の2つの式は同じ意味となります。
utop # not (true && false || not true && not false) ;;
- : bool = true
utop # not ((true && false) || ((not true) && (not false))) ;;
- : bool = true
次に、2つまたは2つ以上の値を比較した結果としてbool型を返す方法について説明します。それは比較演算子を用いる方法です。OCamlには比較演算子として、2つの値が等しいかどうかを表す=、2つの値が異なるかどうかを表す<>、与えられた1つ目の値が2つ目の値よりも大きいかどうかを表す>、与えられた1つ目の値が2つ目の値以上かどうかを表す>=、与えられた1つ目の値が2つ目の値よりも小さいかどうかを表す<、与えられた1つ目の値が2つ目の値以下かどうかを表す<=等があり、これらを用いてその大小を比較することができます4。
utop # 3 = 3 ;;
- : bool = true
utop # 3 <> 3 ;;
- : bool = false
utop # 3 > 2 ;;
- : bool = true
utop # 3 >= 2 ;;
- : bool = true
utop # 3 < 2 ;;
- : bool = false
utop # 3 <= 2 ;;
- : bool = false
utop # "abc" = "abc" ;;
- : bool = true
utop # "abc" <> "abc" ;;
- : bool = false
上記の比較演算子を文字や文字列に適用することも可能です。文字や文字列においては、ラテン文字(abc...xyz、ABC...XYZのこと)の先頭から末尾に行くに連れて大きな値となっており、ちょうど辞書の並びと一致するようになっています。そのため、文字や文字列も整数や実装と同じようにその大小を比較できます。
utop # 'b' > 'a' ;;
- : bool = true
utop # 'b' < 'a' ;;
- : bool = false
utop # "def" > "abc" ;;
- : bool = true
utop # "def" < "abc" ;;
- : bool = false
なお、文字や文字列に大小関係があるからといって、整数や浮動小数点数等の異なる型と比較することはできません。
utop # 'a' < 1 ;;
Line 1, characters 6-7:
Error: This expression has type int
but an expression was expected of type char
utop # "pi" = 3.14 ;;
Line 1, characters 7-11:
Error: This expression has type float
but an expression was expected of type string
最後に蛇足な情報ですが、trueとfalseにも値の大小があるため同様の比較が可能です。
utop # true > false ;;
- : bool = true
utop # true < false ;;
- : bool = false
しかし、これはプログラムの読み手にとって理解しづらい可能性があるため、できるだけ避ける方が良いでしょう。
ユニット値 (Unit value)は()という特別な値です。ユニット値はunitという型で表現され、()という値のみ存在します。ユニット値は戻り値を返す必要がない場合に用います。例えば標準出力への印字といった、いわゆるI/O実行があげられます。
OCamlには標準出力に表示するための関数があり、その1つがprint_endlineです。
utop # print_endline ;;
- : string -> unit = <fun>
print_endlineを用いて標準出力に文字列を表示する例を以下に示します。
utop # print_endline "abcdefg" ;;
abcdefg
- : unit = ()
インタプリタの出力は「直前(-)に入力した結果の型がunit型で、その値が()である」ことを意味します。
プログラミングをやっていると副作用 (side effect)という言葉を聞いたことがあるかもしれません。副作用とは「式が主たる計算(作用)以外の、何らかの状態を変化させる作用」と表現されます。主たる計算以外とは、結果を返すために実行された計算以外の観察可能な計算を指します。具体的には変数(後述)の上書き、ファイル操作やネットワーク通信などのI/O実行を伴う操作が該当します。
OCamlを含む副作用を表現可能なプログラミング言語では、式が副作用を持つかどうかに関心があり、式が最終的にどのような値を返すかに興味がない場合があります。その場合、その計算全体の戻り値をユニット値()とすることで副作用を持つことを明示できます。
ユニット型はCやJavaのvoid型に相当します。voidという用語から空の型を連想しがちですが、実際はUnit型と近い役割を持つとも考えられます。
OCamlにおける基本的なデータは前述のとおりですが、この他にもタプル、リスト、レコード等のデータと型が存在します。これらのデータと型については後述します。