Entries

スポンサーサイト
[EDIT]
上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

-件のコメント

コメントの投稿

新規
非公開にする

-件のトラックバック

トラックバックURL
http://harukazehajime.blog115.fc2.com/tb.php/135-f6aa879b
この記事に対してトラックバックを送信する(FC2ブログユーザー)
OCaml
[EDIT]
最近になってちらほら耳にする機会が増えてきた関数型言語についてもう少し触れてみようと思います。
今回はLISPではなく、Objective Caml(OCamlと略)というプログラミング言語を使います。
OCamlはML(Meta Language)と呼ばれる関数型言語から派生した方言Camlがオブジェクト指向を持つようになったもので、関数型言語の中では比較的シンプルで処理速度にも秀で、日本語の文献もそれなりに整っているという理由から選択しました。
ちなみに元のCamlはCaml-lightとして開発が続けられています。
また、教育用としてMinCamlと呼ばれるOCamlのサブセットが日本人の手によって開発されています。すべてOCamlで書かれていて、コンパイラの学習にも適しています。そしてとくに注目すべきなのは、Microsoftの.NETフレームワーク用のプログラミング言語のひとつとして、OCamlをベースにしたF#という言語が用意されています。つまり、今後OCamlが爆発的に普及する可能性を秘めているということができるでしょう。

まずは難しいことを考えずにちょこっとコードを書いて試してみましょう。関数型言語は難しい用語が出てくるからわけがわからなくなるのです。
xtermなりターミナル.appなりを起動してocamlコマンドを実行します。
Objective Caml version 3.12.0

#

こんな感じに表示されればOKです。うまくいかないときはインストールしなおしてみましょう。
終了させるときは#quit;;と入力してreturnです。quit;;だけではエラーになるので要注意。
ちなみにこの対話環境をトップレベルと呼びます。BASICのようにコードを入力すればその場で実行結果などを表示してくれます。関数型言語の大多数はトップレベルを持っていて、小さなコードを打ち込んでテストを繰り返しつつテキストファイルで全体を組み立てていき、最終的にコンパイラに通してアプリケーションを作り上げます。

それでは例によって階乗を求めるプログラムからはじめます。
let rec fac n =
if n <= 1 then 1 else n * fac (n - 1);;

let recは名前定義のためのキーワード、関数名の指定、引数の定義、=を挟んで次の行に関数本体を記述して、2つのセミコロンで終わっています。トップレベル環境(Perlのように対話的にプログラムをテストする環境)ではダブルセミコロンで式の終端と見なすようになっていて、これによってトップレベル環境での改行を可能にしています。ファイルにソースコードを記述するときは無くても問題ないです。

ifはこれまでさんざん使ってきたので説明するまでもないでしょう...といいたいところですが、気をつけなければならない点をいくつか。
then ... else ... はthenが整数ならelseも整数、thenが小数ならelseも小数というように、値の種類(型)を統一しなければなりません。
もうひとつ、ifはthenもしくはelseを実行して最後に得られた値がif全体の値になります(C言語で言えば?:の三項演算子と同じです)。大ざっぱに言えば、returnを使わなくても、最後に計算した結果を関数の戻り値として返してくれるということです。そのためreturnそのものが存在しません。
ML系の言語で一番厄介なのは、elseを省略したい場合です。関数型言語においてifは途中のプログラムをスキップするというより、条件によって値を選択するという意味合いの方が強くなっています。MLではelseを省略した場合、elseがユニットという値を返す式であると決められています(ユニットはほかの言語でいうnilやnullという値に近いものです)。
ほかの関数型言語ではそれだけで「はいおしまい」と相成りますが、MLではそういうわけにはいきません。MLでの式は常に決まった型の値を返す必要があります。ということで、thenでもユニットを返すようにする必要があります。ユニットは空の括弧()として表されます。

さて、このままでは関数を定義したに過ぎません。実際に関数を使うには引数を与えて呼び出す必要があります。今回の例で4の階乗を求めるときは
fac 4;;
とします。変数に格納したいのであれば、例えば
let answer = fac 4;;
とします。letで変数名を決めて、関数の結果をそのまま変数に格納しています。この辺はBASICにも通じるところがありますね。ただしOCamlではletを省略できないので注意してください。変数名も最初の1文字は小文字かアンダースコア_という制限があります。ということは、OCamlは大文字と小文字を区別するということですね。

四則演算もほかの言語と大差はありません。
let i = (1 + 2) * (3 + 4);;
計算通り21と表示されます。

小数ももちろん使えます。円の面積を求めてみましょう。円の面積はπr2でした。OCamlは円周率が定義されていないので、自分で定義してあげる必要があります。
let pi = 3.14159
let circle r = pi * r * r
circle 2;;

This expression has type float but an expression was expected of type int
最後の最後でエラーが出てしまいました。「この式はfloat型だけどint型を使ってます」というエラーです。
そのほかにも気になる点があると思います。勘のいい人なら2行目は関数定義であることに気付くでしょう。実際、3行目のcircle 2;;がいかにも関数呼び出しであるといわんばかりです。そうすると、let recじゃないと関数にならないんじゃないか?という疑問が出てきます。recとはrecursionの略で、再帰という意味です。ちょうどfac関数は再帰を利用して階乗を求めていました。OCamlの関数定義は、再帰関数でなければrecは不要になります。

ではOCamlは何に対してエラーといっているのか。問題のある部分に下線を引いてくれることを思い出してください。piに下線が引かれています。それからfloatは小数、intは整数です。つまり小数を使うべきところで整数を使ってしまったことが問題となっています。circle 2.0とすれば直るかというと残念ながら直りません。
表示関数で整数用と文字列用とを使い分けたように、演算子も整数用と小数用で使い分けてあげる必要があります。乗算演算子 * は両辺が整数であるときに正しく機能します。片方か両方かを問わず、違う種類の値がくるとエラーになってしまいます。小数同士の乗算は *. 演算子を使います。右下はゴミではなくピリオドです。つまり
let pi = 3.141592
let circle r = pi *. r *. r
circle 2.0;;

とすることで直せます。circle 2;;ではなくcircle 2.0;;であることにも注意してください。勝手に小数に変換してくれないので、引数として与える半径も小数である必要があります。
piを手入力でなく計算式からの近似値を求めたい場合、6.0 *. asin 0.5(逆正弦の1/2の展開)や4.0 *. atan 1.0(逆正接の1の展開)などとするとよいでしょう。また小数に限って冪乗演算子(**)が定義されているので、
let pi = 6.0 *. asin 0.5
let circle r = pi *. r ** 2.0
circle 2.0;;

とするとよりスマートです。
小数には専用の演算子があるということは乗算に限らず四則演算すべてにいえます。
この辺がOCamlの厄介なところですが、慣れるしかありません。型についてものすごく厳格である分、でき上がったプログラムはプログラマー本人でさえ意図していなかったミスも非常に小さくなります。
例えば、Javaでは
System.out.println("2+3= " + 2 + 3);
というコードを書いたとき、プログラマーは"2+3= 5"と表示されるものだと思ってしまうでしょう。しかし、+演算子は左辺の優先順位が高いので、まず"2+3= " + 2を実行して"2+3= 2"という文字列を作ってしまいます。すると残りも自明ですね。これを回避するためには2 + 3を括弧で括って先に足し算を実行させる必要があります。
OCamlでは
print_string ("2+3= " + 2 + 3);;
とすること自体不可能です。整数の表示にはprint_intがあるので
print_string "2+3= "
print_int 2 + 3;;

で...解決しません。関数の引数はスペースで区切って複数列挙します。が、print_intが受け付ける引数はひとつです。print_int関数と引数2があって、ほかに+ 3という余分な引数があると勘違いしてしまうので
print_string "2+3= "
print_int (2 + 3);;

のように括弧で括ってあげます。最後に改行がほしいときはprint_newline ();;を付け足せばOKです。
なんとも面倒くさい言語ですが、とにかく慣れです。

さて、ここまで簡単なサンプルを見てきましたが、あまりにもありふれたコードで、わざわざ関数型言語を学ぶメリットを見出すことはまず無理でしょう。それもそのはず、取っつきやすいように関数型言語らしい部分を隠して説明してきたのですから。
それではより深く掘り下げてみましょう。

関数型言語は、関数さえも変数に割り当てたひとつの値であるかのように扱うことが可能です。たとえばこんな関数を考えてみます。
let calc f x y = f x y;;
これだけでは当然役に立ちませんが、引数fに関数を割り当てることができて、引数xとyが適用された関数fが評価されたら、かなり汎用的な関数になりそうです。高階関数はそれを可能にします。適当にこんな関数を作ってみましょう。
let add x y = x + y
let multi x y = x * y
let div x y = x / y
let sub x y = x - y;;

今定義した関数を引数にして、先ほどのcalcを使って呼び出してみます。まず足し算から。
calc add 1 2;;
これで引数fにaddが、xに1、yに2が渡され、さらにaddが呼び出されて1 + 2を計算します。結局、calcの結果として3が返ってきます。
ではmultiを渡してみましょう。
calc multi 1 2;;
1 * 2が実行されて2が返ってきます。ほかの関数についても試してみてください。
このくらいではいまひとつ魅力が伝わらないでしょうか。たしかにこのような関数はC言語でも可能ですが、ポインタを使う必要があり、潜在的なバグを作り込んでしまう可能性があります。関数型言語はポインタを使うことなく高階関数を書くことができます。

面倒くさい制限がたくさんありますが、関数型言語といってもそこらにある言語と大差ない、むしろかなりシンプルにコードを書けることがわかっていただけたかと思います。
※この面倒くささはMLという言語特有のもので、関数型言語そのものの特徴ではないことにも注意してください

おまけ
wikipediaのアルゴリズムを用いた累乗関数
1. 指数を n とし、2乗していく値 p := a 、結果値 v := 1 とする。
2. n が 0 なら、v を出力して終了する。
3. n の最下位桁が 1 なら、v := v * p とする。
4. n := [n/2] とし(端数切り捨て)、 p := p * p として、2. に戻る。
アルゴリズムの疑似コード
fun pow n, p, v
2: if n = 0 then return v
3: if (n bitAnd 1) = 1 then v := v * p
4: pow (n / 2), (p * p), v
end
1: i = pow n, a, 1

実装結果(整数専用)
let rec pow p n v =
match n with
0 -> v
| y -> let new_v =
if (y land 1) = 1 then v * p else v in
pow (p * p) (y / 2) new_v
let pow x y = pow x y 1


次回はさらに深く掘り下げていく予定です。
関連記事

0件のコメント

コメントの投稿

新規
非公開にする

0件のトラックバック

トラックバックURL
http://harukazehajime.blog115.fc2.com/tb.php/135-f6aa879b
この記事に対してトラックバックを送信する(FC2ブログユーザー)

Appendix

プロフィール

さくらゆーな

Author:さくらゆーな
鉄道熱が再燃して、撮影に模型にいろいろやってます。
最近反核運動に偏ってるのを反省したいけど
知れば知るほど極悪非道な界隈で止まらない…

カレンダー

06 | 2017/07 | 08
- - - - - - 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 - - - - -

+ アーカイブ
 

最近の記事

カテゴリー

検索フォーム


キーワード

カウンター

トータルカウンター
現在の閲覧者数:

ads3

Mac ソフトのことなら act2.com

Make a donate

もしこのブログを気に入っていただけたら上記アフィリエイトプログラムか下のPayPalでブログ・サイトの維持にご協力ください。

donationPrice

ブロとも申請フォーム

この人とブロともになる

ブログランク

上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。