はじめに
Lispという言語を聞いたことがないエンジニアも多いかもしれません。「なんだか難しそう」と感じる方もいるでしょう。
(自分もその一人でした。)
「神の言語」とも呼ばれるLisp。そんなに面白いならやってみよう!ということで、私も自分なりにLispを学んでみることにしました。
Lispの魅力を熱心に伝えるために、そのせいで公平性を欠いた記事となっている可能性はありますが、Lispの魅力を伝えたい一心であり、他の言語・FW等を否定する意図は一切ございません。それぞれに良さがあることを理解したうえで、Lispの魅力に焦点をあてて書いたつもりです。
興味本位で足を踏み込んでみた完全初学者です。完全初心者です!
常日頃Lispを扱っているLisperと比べると取るに足らない存在なので、厳密な言葉の意味が違ったり、誤ったことを言っているかもしれませんがご了承ください。その際は優しくコメントなどで教えていただけると嬉しいです。
Lispとは?
Lispは、COBOLなどと並んで非常に古くからあるプログラミング言語です。Lispという名前は「List Processor(リスト処理)」に由来しています。その名の通り、Lispはリストを操作するための言語です。
リストとは?
リストとは、数値や文字列など、さまざまな型のデータを要素として持つことができるデータ構造です。リストの中にリストを含めることもできます。そのため、リストは非常に柔軟で強力なデータ構造です。
簡単に言うと、「まとまり」と考えて良いのかなと思っています!
(setq my-list '(1 "apple" (2 "banana"))) ; リストの例
ちなみにJSで書くならこんな感じだと思います
let myList = [1, "apple", [2, "banana"]]; // 配列の例
Lispの特徴
Lispの最大の特徴は、データとしてのリストとプログラムとしてのリストが同じ形式で表現されることです。つまり、リストとして作成したデータをプログラムとして実行することができるのです。
(defun square (x)
(* x x))
(square 4) ; 16
この例では、squareという関数を定義し、それを呼び出して結果を得ています。このように、プログラム自体がリストで構成されています。
これだけだと分かりにくいかもしれないので、他の言語と比較してみます!
上の例をJSで書くとこんな感じです!
// 関数の定義
function square(x) { return x * x };
// 呼び出し
console.log(square(4)); // 16
Common LispとScheme
Lispにはいくつかの方言がありますが、最も広く使われているのはCommon LispとSchemeです。どちらもLispの基本的な概念を共有していますが、用途や特徴が異なります!
(あまり調べられてないので割愛とさせてください!方言があるんだ〜という理解で一旦は良いと思います!)
Lispの基本例
実際にLispのコードを見てみましょう!以下にいくつかの基本的な例を示します。
変数の定義
(setq x 10) ; 変数xに値10を代入
関数の定義
(defun greet (name)
(format t "Hello, ~A!~%" name))
(greet "Alice") ; "Hello, Alice!"
リストの操作
(setq my-list '(1 2 3 4 5)) ; リストの定義
(first my-list) ; リストの最初の要素を取得 (1)
(rest my-list) ; 最初の要素を除いたリストを取得 (2 3 4 5)
ここまでやると流石に気付きますよね・・・
ひたすらにカッコが多い!! そして語順にクセがある!
語順にクセがある
ちょっと端折ってましたが、語順のクセを実感するのが四則演算でした。
JSでは下記のように書くのですが、
console.log(3 + 5); // 8
console.log(4 - 1); // 3
console.log(2 * 4); // 8
console.log(8 / 4); // 2
Lispだとこうなります
(print (+ 3 5)); // 8
(print (- 4 1)); // 3
(print (* 2 4)); // 8
(print (/ 8 4)); // 2
はじめて見た時には「?!?!?!?!」という感じでした。
けど、Lispを少しずつ学んでいくにつれて、
「なるほど!こっち(Lisp)の方が美しいのかもしれない!」
と感じるようになりました!
「読みづらい」じゃない
Lispの特徴の一つは、そのシンプルで一貫性のある構文です。Lispは非常に少ない基本構文で成り立っており、このシンプルさが「美しい」と評価される理由の一つです。
例外が少ない
Lispは他の多くのプログラミング言語に比べて、例外や特別なケースが少ないです。これはLispが極めて一貫性のある言語設計を持っているためです。Lispのすべてのコードはリスト構造を基盤としており、これがシンプルさと強力さを両立させています。
他の言語との比較
例えば、他の言語ではfor文は、for用のルールがあるんです。
JSの場合
for (let i = 0; i < 5; i++) {
console.log(i);
}
このコードでは、次のような処理が行われています
- let i = 0; で変数 i を0に初期化します
- i < 5; という条件がtrueである間、ループを繰り返します
- 各ループの最後で i++ により i を1ずつ増加させます
- console.log(i); によって i の値を出力します
Lispの場合
(loop for i from 0 to 4 do
(print i))
このコードでは、次のような処理が行われています
- loopはループの開始を示します
- for i from 0 to 4は、変数 i が0から4までの範囲で繰り返す処理を定義します
- doは、ループ内で実行するアクションを指定します
- (print i)は、各ループのたびに i の値を出力します
Lispのloop構文は、最初は独特に見えるかもしれませんが、非常に直感的で強力ですね!
・
・
・
ん?!?!
全然直感的ではないですよね・・・
Lispの構文の統一性
Lispの構文は非常に統一されています!すべての処理がリスト形式で統一されており、構文が少ないため、ルールを覚えやすく、プログラムの可読性が高いのです!
そしてこの統一性は、loop構文にも適用されます。
足し算の例と比較
(+ 3 5) ; 3 と 5 を足す
この構文では、+ が処理を示し、3 と 5 が対象です。
同様に、loop構文も以下のように解釈できます
(loop for i from 0 to 4 do (print i))
ここでは、loop が処理を示し、for i from 0 to 4 と do (print i) が対象です。
もっと言うとこんな感じです!
(loop ; ループの開始(処理)
for i from 0 to 4 ; i を 0 から 4 まで繰り返す(対象)
do ; ループ内のアクションを指定(処理)
(print i)) ; i を出力する(対象)
Re: Lispの構文の統一性
Lispのloop構文は、「処理」と「対象」のシンプルな構図に従っています。これは、足し算や他の基本的な操作と同じパターンです。Lispのシンプルさと一貫性が、プログラムの可読性と理解のしやすさを向上させていると言えると思います!
Lispのカッコとその美しさ
Lispを初めて見ると、多くの人が「カッコが多い!」と感じるかもしれません。しかし、このカッコの多さこそが、Lispのシンプルさと美しさを象徴しているのです!
Lispは、文法の基本ルールを極限まで削ぎ落とすことで、非常に一貫性のある言語設計を実現しています。
他の言語との比較
他の言語では、複数の記号や構文ルールが存在し、それぞれ異なる使い方を覚える必要があります。例えば、PHPのコードを見てみましょう。
function greet($name) {
echo "Hello, " . $name . "!";
}
for ($i = 0; $i < 5; $i++) {
greet("World");
}
PHPでは、関数の定義にfunctionというキーワードや{}、()が必要です。またループにはforキーワードと{}が使われ、($i = 0; $i < 5; $i++)はforループ独自の構文になっています。
PHPはこうした構文の使い分けによって、コードの可読性を高めていると言えます。
一方でLispは、よりシンプルで統一的な構文を採用することで、別の種類の可読性と表現力を実現しているのです。
(defun greet (name)
(format t "Hello, ~A!~%" name))
(loop for i from 0 to 4 do
(greet "World"))
Lispでは、関数定義もループも同じリスト形式で書かれています。この統一された形式により、覚えるべき構文ルールが少なくて済みます。
見比べてみると、このシンプルさと美しさが実感できるのではないかなと思います。
すごいなと感じたところ
シンプルで一貫性のある構文
(前述しているので割愛)
強力なマクロシステム
Lispの例
Lispのマクロは、プログラムのコードを生成、変形するための強力なツールです。以下は、unlessマクロの例です。
(defmacro unless (condition &body body)
`(if (not ,condition)
(progn ,@body)))
(unless (= 1 2)
(print "1 is not equal to 2"))
JavaScriptの例
JavaScriptにはマクロはありませんが、同じようなこともできます
function unless(condition, func) {
return function(...args) {
if (!condition) {
return func(...args);
}
};
}
const test = unless(1 === 2, function() {
console.log("1 is not equal to 2");
});
test(); // 出力: 1 is not equal to 2
高度な抽象化と自己言及性
高度な抽象化
Lispの例:高階関数
Lispでは、関数を他の関数の引数として渡したり、関数を返す関数を作ることが非常に簡単です。
(defun apply-twice (f x)
(funcall f (funcall f x)))
(defun square (x)
(* x x))
(apply-twice #'square 3) ; 出力: 81
apply-twiceは、関数fを2回適用する高階関数です。
JavaScriptの例:高階関数
同様のことをJavaScriptで行う場合も比較的簡単です。
function applyTwice(f, x) {
return f(f(x));
}
function square(x) {
return x * x;
}
console.log(applyTwice(square, 3)); // 出力: 81
自己言及性(メタプログラミング)
Lispは、プログラムをデータとして扱うことができ、これを利用して自己言及的なプログラムを書くことが容易です。
(defun eval-and-print (code)
(print (eval code)))
(eval-and-print '(+ 1 2 3)) ; 出力: 6
この例では、eval-and-print関数がLispコードを評価してその結果を出力します。
JavaScriptでもeval関数を使って自己言及的なプログラムを書くことができますが、あまり推奨されていないようです。
function evalAndPrint(code) {
console.log(eval(code));
}
evalAndPrint('1 + 2 + 3'); // 出力: 6
まとめ
Lispを少しでも学んでみると、この美しさに感動するのではないかなと思います!
特に「1+2」という書き方の方が例外なんだ!というのが面白い気付きでした!
実際に複雑な処理とか書いてないのですが、純粋に「美しい!!」と強く感じ感動しました!
たまには普段触らない言語・FWに触れることで新たな気付き・学びがあるので例えば土曜日一日使って新しいコトを学んでみるというのも面白いのかもしれません!
簡潔で美しいルールで運用されているLispを是非少しでも触ってみてはいかがでしょうか?!
ぜひ一度Lispを書いてみて、その魅力・感動を直接感じていただければと思います!
もし
- この記事のLisp観に間違いがあると感じた場合
- Lispのここも面白いよ!と共有したい場合
など、ご意見やご感想がありましたら、ぜひコメントで教えて下さい!
参考
Land of Lisp
↓勉強する中で分かりやすく面白く解説していたサイトです、ありがとうございました!↓
マンガで分かるLisp
魔法言語 リリカル☆Lisp