新人プログラマがLISPを学んだらプログラミングの見方が変わった
2018年LISPアドベントカレンダー5日目の投稿です。
LISPの悟り
新人プログラマが学ぶべきプログラミング言語を説く解説記事は星の数ほどあります。
そのどれかに記述があったのでしょうか、いつしか「LISPという難解な言語を触ると悟りが得られる」と知りました。
では、プログラミングの見方が変わる「LISPの悟り」=「LISPを学ぶことの効用」とは一体なんでしょうか。気になってネットの記事を読んでみました。すると、下記の一文に目がとまりました。
この悟りは、コンピュータを利用する上で「データは人間の為だけのものじゃない」ことを実感するような経験によることでしょう。 「Lispの悟りが分かっちゃう新春ポエム」
この内容、初読では理解できませんでした。平易な日本語なのに内容を理解できないのはどこか居心地が悪いものです。そしてこの悟りとは何だろうという好奇心が自分を後押しして、LISPを実際に触ってみることにしました。
以下では、プログラマ1年生の自分が、「Land of Lisp」というLisp学習のバイブル本を読んで得たLISPへの理解、引いてはプログラミングに対する見方が変わった点を紹介したいと思います。
LISPのコードとデータ
LISPにはコードモードとデータモードがあります。 コードは関数を実行するもの(コマンド・命令)。データは文字通り情報です。
累乗を計算するexpt関数で例を見てみましょう。
;; コードモード
(expt 2 3) ;; 2^3 = 8
;; データモード
'(expt 2 3) ;; (expt 2 3)
コードモードでは、リストの先頭の要素expt
がコマンドで、リストの残りの要素はこの関数の引数です。全体を通して、「2を3乗する」という処理になっています。
一方、データモードではリストの先頭に'(シングルクオート)
をつけます。このように記述すると、LISPではシングルクオート以降のリストはデータとして扱われるため、コードが実行されることはありません。
プログラミングに対する悟り
プログラミングはデータのインプットとアウトプットをするアプリケーションを作成することです。アプリケーションの内部ではデータのCRUD、つまり作成・読み取り・更新(加工)・削除といった処理を行なっています。プログラミングパラダイムが手続き型であれ、オブジェクト指向型であれ、関数型であれ、この点は同じだと思います。
このように考えると、プログラミングは、このコマンド(=データ処理)の組み合わせだと考えることができます。
オブジェクト指向の世界
一方、オブジェクト指向では、クラスというものは一般的に下記のように説明されます。
「クラスは、データと振る舞いをカプセル化したものである」 参考:プロパティ?フィールド?メンバー?C#のクラス構造のおさらい
オブジェクトはメソッドとプロパティで構成されています。LISPの考え方を援用すると、メソッドはコマンド(振る舞い)、プロパティはデータと言い換えられるのではないか。そう思い至った時、オブジェクト指向の見方が変わりました。
オブジェクトのメソッドの役割はデータを処理するだけなのだ考えると、プログラマがメソッド内で文字列やマジックナンバーをローカル変数に格納することを避ける理由がわかりました。そのオブジェクトで持つにふさわしいデータはコンストラクタで受け取り、プロパティに持たせるべきです。
メソッドの引数にデータを入れると、返り値としてデータが返ってくる。メソッドの中身はコマンドです。オブジェクト指向ではプロパティをメソッド内で参照することがあります。この時「メソッドは引数以外のデータ、しかもそのクラス内で存在しているデータを参照しているのだ」と意識するようになりました。
データはオブジェクトのプロパティに、あるいは定数としてオブジェクトの外に持たせるべきである、と思うに至りました。
通り一遍にオブジェクト指向を学べば、入門書にも書いてある内容でしょう。しかし、「classは型で、objectはモノ。例えばclassは車の型(車体)で、プロパティは車の色」と書かれたような入門書で、この理解にたどり着くことは不可能です。
LISPを学ぶことで初めて、クラス、メソッド、そしてプロパティの関係を深く理解することができました。
また、プログラミングで処理するデータソースは多種多様です。**JSONはデータの塊、CSV,TSVも拡張子こそ違えど、抽象化した視点から見れば等しくデータの塊です。**どこから読み取ってもデータはデータ。このデータの中には、データ自身を操作するプログラムが入り込む余地はありません(例外としては、エクセルはデータとシート内のデータを操作する関数を持っていますね)。
つまり、オブジェクト指向は、データを取得してプロパティに格納し、メソッドでデータを操るプログラミングです。
(もちろんオブジェクト指向はこれだけではありません。オブジェクト指向はオブジェクト同士のメッセージの伝達により、処理を行うプログラミングの方法です。ここでは特にプロパティとメソッドについて記述をしました)
データとコマンドはプロパティとメソッドに対応している。そして、プログラミングはデータとデータ処理のことだということがプログラミングに対する悟りです。
LISPの世界
一方、LISPにはオブジェクトもプロパティはありません(CLOSの議論は省きます)。
LISPの関数は第一級(first-class function)です。第一級の関数は、文字列や数値と同じように関数の引数として使用したり、配列に格納することができます。
つまり、LISPの関数(コマンド)はデータとして扱うことができます。
先ほど紹介した記事は最後にこのように締め括られています。
Lispは色々なパラダイムと組み合わせられますが、何がLispかといえば、 プログラム = データ であることに尽きるのではないでしょうか。
関数すらデータとして扱うことのできるLISPでは、データこそがプログラムなのです。
コンピュータの「演算」に対する悟り
次はデータに対する演算について考えていきます。
算術演算子と論理演算子
データは人間のためだけのものではありません。コンピュータがデータを処理するにあたって、データに型(type)を与え、コンピュータで扱えるものにする必要があります。
例えば、人間には同じように見えるけれども、文字列型と数値型で計算の結果が異なることは普通のプログラマなら誰でも知っていることです。 (以下はJavaScriptのコードです)
const result1 = 1 + 1; // int型
const result2 = "1" + "1"; // string型
console.log(result1); // 2
console.log(result2); // 11
float型とint型もややこしいですね。人間から見れば1
も1.0
も同じ1
。でもコンピュータから見れば別物なのです。
では、論理型の計算はどうでしょうか。論理型には論理和、論理積という計算方法があります。この計算のために、多くの言語では論理演算子に||
, &&
, !
が定義されています。
const now = new Date();
const day = now.getDay();
const isSevenOClock = now.getHours() === 7;
const isWeekDay = [1,2,3,4,5].includes(day);
if (isSevenOClock && isWeekDay) {
// 朝起きる
}
このプログラムを実行するタイミングが月曜日の朝7時である時、if (true && true)
に見えますか?また、これがコンピュータによるboolean型の計算であるように見えますか?
&&
は演算子です。コンピュータから見れば、1 + 1
の+
と同じ役割なのです。しかし、人には+
と&&
は直感的には同じような演算子という認識を持つことは難しいでしょう。
しかし、LISPなら状況は違うのです。
LISPの論理演算
以下は足し算をする処理です。
(+ 1 1)
ご想像の通り、結果は2
です。
次は値を比較してみましょう。
(eq (+ 1 1) 2)
このコードは(eq 2 2)
として評価されます。結果はT
(true)です。
次は論理演算子を使ったコードをみてみましょう。
(and (eq 1 1)
(eq 2 2))
このコードは(and T T)
として評価されるため、結果はT
(true)です。
&&
とand
は同じ役割をしています。が、LISPの方がより一層「演算」という役割が明確にされています。コンピュータでは数値も文字列も真理値も演算することができます。
人間にはtrue && false
はfalse
に見えますが、コンピュータは1 and 0
は0
というように見えています。1 plus 1
と1 and 1
。1 + 1
と1 && 1
。同じ計算を指していても、コンピュータと人間の間で記号が違うだけなのです。
ここまで読んでくださった方は、これから条件式を記述するために&&
や||
、!
と打ち込むときに、「今自分は論理の計算をしているのだ」と意識することになるでしょう。それが演算に対する悟りです。
(論理演算について詳しく知りたい方は「あなたは論理演算がわかりますか?」を一読してみてください)
次なる悟りへ
ここまで読んだあなたは、本稿を読む前に比べてプログラミングに対する、またコンピュータに対する見方が変化したのではないでしょうか。 他にも再帰関数やマクロ、コンスセル(リスト)というデータの格納方法など、LISPにはプログラミングの見方を変えてくれる要素がたくさんあります。
この文章を通して、データとは何か、functionとは何か、演算とは何か、今まで見てきたものが、全く違う見方になれば幸いです。
あなた自身のさらなる悟りを得るために、ぜひLISPに入門してみてください。
Happy Coding 🎉