はじめに
OCamlのモジュールシステムとCommon LispのCLOSは、どちらもプログラムを構造化し、再利用性を高めるための強力な仕組みです。しかし、そのアプローチは大きく異なります。今回は、図形を描画するという同じ具体例を使って、それぞれの違いを見ていこうと思います。
OCamlのモジュールシステムとCommon LispのCLOSは、どちらもプログラムを構造化し、再利用性を高めるための強力な仕組みです。しかし、そのアプローチは大きく異なります。今回は、図形を描画するという同じ具体例を使って、それぞれの違いを見ていこうと思います。
Common Lisp(私が使用しているのはSBCL)では、老眼に優しくないクオートとバッククオートがそれぞれ違う機能として使われています。(これらはリーダーマクロもしくはリードマクロと呼ばれているもので、実はこれ自体を変更するset-macro-characterやset-dispatch-macro-charaterなんていう関数も存在しますが、今回は触れません)
マクロを定義する時、このThe ROUGANに優しくないクオートとバッククオートを使います。
SBCLインストール後、sbclコマンドでREPLを起動し、以下のようにASDFのバージョンを確認します。この際、rlwrapをインストールしておくと使いやすくなります。
* (require 'asdf)
* (asdf:asdf-version)
"3.3.1"
OCamlのListモジュールにはpartitionという便利な関数がありまして、これを使うとクイックソートが簡単に書くことができます。
let rec qsort = function
| [] -> []
| h :: t ->
let (l, r) = List.partition (fun x -> x < h) t in
(qsort l)@[h]@(qsort r)
こりゃ便利!と思い、他の言語でも使いたい!ということで自前で書くことにしました。
「OCamlのL99問題をCommon Lispで書いてみる」ことで、OCamlとCommon Lispを同時に勉強するという無謀な方法をしばらく地道に続けている私ですが、素人発想ながら、意外と効果があるのではないかと最近思っています。
Huffman codeについてはWikipediaの解説が分かりやすいので、ここでは割愛しますが、紙と鉛筆を使ってハフマン木を書いてみるとわかりやすいように思います。
さて、プログラムの話になりますが、今回はOCamlにあってCommon Lispに無いものでいろいろ悩みました。
OCamlでは値を持てる列挙型みたいなヴァリアント型というものがあります。これは再帰にすることもできます。Rustのenumと似ています。しかし、Common Lispで同様のことを行うのはなかなか難しかったです。Common Lispにも列挙型的なものはあるようで、以下のような形で定義するようです。
オイラーのトーシェント関数とは「正の整数nに対して、nと互いに素である1以上n以下の自然数の個数φ(n)を与える数論的関数φである!」……え? 数論的関数??
ちょっと何言ってるか分からない。
「自然数」「互いに素(タガイニソ)」はなんか聞いたことがあるようなないような気が……
ここでL99-33の単純な問題の意味を悟る。
「aとbが互いに素」とは、aとbを共に割り切る正の整数が1のみである。つまり、aとbの最大公約数が1であるということ。「互いに素」は英語でcoprimeという……、ふむ。そうL99-33の問題は単純な問題ではなく、「互いに素」かどうかと判定する関数でありました。
OCamlのL99問題の32問目は2つの正の整数の最大公約数(the greatest commom divisor)を求めよ、という問題。これは私自身、過去にやったことがあるので、できる気がします(記憶が確かなら)
ユークリッドの互除法は例えば a = 10 b = 8 とした時、以下のような流れで計算していきます。
といった流れで求めることができます。
素数とは1より大きい自然数で、正の約数が1とそれ自身のみという数字のことで、私は学校での知識としての素数より、映画「博士の愛した数式」で寺尾聰さん演じる博士がもっとも愛した数字としての印象が強いです。この映画では素数の他にも「完全数」「友愛数」なども出てきます。この映画で、数学に興味を持った方は意外と多いのではないでしょうか。
今回はその博士に最も愛された素数についての問題をやってみます。これはこのところずっと勉強しているOCamlのL99の31問目が素数の問題だからですが、実は素数判定にはやり方が色々あるようで、OCamlのL99-31のドキュメントには「より賢い解決法として「エラトステネスの篩(ふるい)」というものがあるから調べてみて」と書かれています。
しばらく記事の更新が遅れてしまってました。実はここのところOCamlのL99問題の答えを読みながら、それをCommon Lispで書いてみるということをずっとやっていました。
なぜこのようなやり方をしているのかと言いますと、そもそも私はOCamlにはまだ慣れていないし、Common Lispも初心者という状態です。しかし、年齢的にも生活的にも時間が限られている。したがって、少しばかり理解しているOCamlのコードを読むことでより一層OCamlに慣れ、さらにCommon Lispで書くことによって、Common Lispにも慣れていく、という方法を思いつきました。そもそもL99問題の解答は、プロの方がベストなものを提供してくれているわけですから、よい見本にもなるのではないか、と思います。
(defun range-normal (start end &optional (step 1))
(if (> start end)
'()
(cons start (range-normal (+ start step) end step))))
これを使って、1から1000までをstepを3でリストを作ってみます。car/cdr的なものは無いか?
fn main() {
let v = vec![1, 2, 3, 4, 5];
let (hd, tl) = v.split_first().unwrap();
assert_eq!(hd, &1);
assert_eq!(tl, &[2, 3, 4, 5]);
}
split_firstは、Option<(&T, &[T])>を返します。これを利用すればcar/cdr関数と同じように処理できます。split_first_mutというものもあり、こちらはミュータブルな参照を返します。car/cdrという名前で使いたい場合は以下のような関数を作成すればいいかと思います。lastという関数を使えばすぐに結果が得られるような…でも問題の意図は違うところにありそうな…」cond文