[神の言語] Lispに触れてみた話

こんにちは!AdventCalender2024 16日目担当のyukiです.
今回は,以前からずっと気になっていた,熱狂的なファンがいることで有名なLispを試しに触ってみた話をしようかなと思います.
良かったら読んでみてください!

はじめに

Lispとは,すべてのデータとコードを()で囲われた式として表現するプログラミング言語で,計算式の記述やデータ,関数の定義,呼び出し,制御などほとんどすべての構成要素を()で括ったリストで表すというなにやら凄そうな言語です.

1958年にMITのジョン・マッカーシー氏により開発されたLispは,非常にアカデミックな言語でFORTRANに次いで古い高級言語のひとつになっています.
人工知能の研究用言語として主に使用されていたことでも有名ですが,その数学由来の文法の美しさと拡張性によって,これまで熱狂的なファンを多く生み出してきました.

この記事はあくまでLispを軽く触ってみた感想なので温かい目で見ていただきたいのですが,面白い言語だなと感じたので共有したいと思います.

Lispを書いてみよう

Lispはその拡張性の所以から方言がいくつかあるのですが,その中でも有名で実用的なCommon Lispを導入します.

clispのダウンロード

sudo apt update
sudo apt install clisp

Linux
上のコマンドで簡単に導入できます

Windows
こちらのサイトからダウンロードできます
https://sourceforge.net/projects/clisp/

brew install clisp

macOS
上のコマンドで簡単に導入できます


導入ができたらターミナルにclispと入力することで,自動的にREPLというインタプリタ型の実行環境に入ることができます.以下のようになればOKです.

  i i i i i i i       ooooo    o        ooooooo   ooooo   ooooo
  I I I I I I I      8     8   8           8     8     o  8    8
  I  \ `+' /  I      8         8           8     8        8    8
   \  `-+-'  /       8         8           8      ooooo   8oooo
    `-__|__-'        8         8           8           8  8
        |            8     o   8           8     o     8  8
  ------+------       ooooo    8oooooo  ooo8ooo   ooooo   8

Welcome to GNU CLISP 2.49.93+ (2018-02-18) <http://clisp.org/>

Copyright (c) Bruno Haible, Michael Stoll 1992-1993
Copyright (c) Bruno Haible, Marcus Daniels 1994-1997
Copyright (c) Bruno Haible, Pierpaolo Bernardi, Sam Steingold 1998
Copyright (c) Bruno Haible, Sam Steingold 1999-2000
Copyright (c) Sam Steingold, Bruno Haible 2001-2018

Type :h and hit Enter for context help.

[1]>

こっから先はよくわからないことになったら :qと打てばとりあえずは大丈夫です.

特徴的(個人的)な文法を簡単に整理

前提として最初にも少し述べたのですが,Lispの文法というのは非常にシンプルで,コードを構成する方法は()を使ってリストにするだけです!(これが初心者の僕を苦しめましたが)

ただ後にも述べますが,Lispのコードは,(コマンド 引数1 引数2 ...) というフォームという構造(コマンドは通常関数)をとる必要はあり,この後出てくるコードはすべてこの形に従っているのが分かると思います.

これだけではよく分からないので早速コードを見ていきましょう!

四則演算

四則演算ですが,いきなりLispがLispである所以が分かります.Lispで3+5の結果を出力するためには

>(+ 3 5)
8

とオペランド記法で記述する必要があります.
これは,Lispが最低限のルールで書くためであり,+を演算子とするというルールすらLispにとって無駄であるということでしょう.
Lispにおける +は関数であって 後ろの3と5がそれぞれ引数であると捉えると,割と理解できると思います.

グローバル変数の定義

グローバル変数の定義は以下のように定義できます.(* で括っているのは,ローカル変数と区別するための慣習だそうです.)

>(defparameter *a* 3)
*A*
>*a*
3

変数に数値が代入されていることが分かります.

グローバル関数の定義

グローバル関数を定義するには以下のように書きます.試しにn*(n-1)を定義した関数を書いてみます.

>(defun ex_cal(n) (* n (- n 1)))
EX_CAL

オペランド記法とかっこの多さに悶絶してください.

>(ex_cal *a*)
6

この関数を使ってみると期待される出力になっています.

コードとデータ

Lispにはコードとデータという2つの概念があるのですが,これは至って単純で,コードとして書いたものはコンピュータは実行しようとするし,データとして書いたものは実行せず,ただのデータとして扱うというだけです.

ただここで重要なのは,コードとデータがLispはどちらもリストで構成されているために,コードがデータであり,データがコードであるという特性を持っていることです.
これが後で話すLispのマクロというものを超強力なものにしています.

コードは,上で述べたようにフォーム型である必要がありますが,データにしたい時は()の前に'を付けるだけでよいです.

>'(orange apple)
(ORANGE APPLE)
> '(+ 2 3)
(+ 2 3)

上ではコードであったものもデータにできます.

なぜLispを書くのか

ここまで簡単な文法を見てきましたが,なぜ他の言語があるにも関わらず,Lispを書く必要があるのか,非常に疑問だと思います.その答えとして一番にあがるのは,マクロでしょう.

マクロとは

Lispのマクロは,簡単に言うとコードを書くためのコードです.このように僕がコードを書いたら,こうLispのコードに変換してね,とあらかじめ伝えておくのがマクロのイメージです.書き方はおおよそ以下の通りです.

>(defmacro コードを書くためのコード '変換語のLispコードのデータ形)

例えば何度もこのコードを書くのは長くて面倒だなという時にマクロを書いておくことによって,効率化することができます!

ただそれは関数ではだめなのかという疑問があると思います.大きく違うのは,関数は引数が即時に評価されるのに対して,マクロは引数が評価される前にそのままの形でマクロに渡される点です.
(具体的な例は下でやります)

マクロを使うと,他の言語ではあらかじめ定められている言語仕様(ifやforなどの構文)を勝手にカスタマイズしたり,自分で新たに生み出したりすることができるようになります!

なので,自分の好きなように言語仕様を変更したり付け加えたりして,特定のタスクを行うのに特化した言語を作ったりすることができるようになるわけです.(だから人工知能の研究に使われていたのかも)

メタプログラミングについて

メタプログラミングとは,プログラム自身がコードを生成する技術のことで,マクロはまさにそれに当てはまります.Lispは基本ルールが極めて単純で,コードとデータを同じように扱うことができるので,右に出る言語がいないほどその拡張が柔軟です.

特にLispのインタプリタ(コンピュータがコードを解釈できるようにするためのソフトウェア)をLisp自身で書くことができてしまうという話は本当に驚きです...

マクロを使ってみる

マクロを試しに書いてみましたが,なかなか難しいです.(正確にマクロを使えていなかったらすみません)

1.(+ 3 5)を(3 + 5)にしてやる
Lispに対しての冒涜かもしれませんが,やってみました.
まず,関数でこれを実現しようとすると,

>(defun infix(a op b) (op a b))

となって,作った関数を使おうと(infix 3 + 5)と打つと,やはり引数が先に評価されてしまい,+は関数なのでLispの言語仕様に縛られて文法エラーが出てしまいます.

そこでマクロを使います.

>(defmacro infix (a op b) `(,op ,a ,b))

>(infix 3 + 5)
8

と書くと,出力されました!
ここでは(infix 3+5)と僕が書いたら(infix + 3 5)に変換してコンパイルしてねと伝えたというのがイメージです.

2.Lispのfor文をpython風にしてみた
Lispのfor文がかなりいかつかったので,python風にしてみたいと思います.

>(do ((i 1 (+ 1 i)))
((> i 5))
(print i))

まずはLispのforループ

>(defmacro for (var (start end) &body body)
`(do ((,var ,start (+ 1 ,var)))
((>= ,var ,end))
,@body))

マクロで書き換えたもの

>(for i (1 5) (print i))
1 
2 
3 
4 
NIL

結果 かなり書きやすくなりました.

今回は簡単な例のみになってしまったので,マクロの驚異が感じられるまで自分で勉強したいと思います!!

まとめ

・Lispはすべてリスト形で記述するという超最低限のルールで構成されており,極めて拡張性が高い故にLisperと呼ばれる信者を生み出してきた
・LispをLispで書くことさえできるマクロという機能が存在し,超強力
・Lisperへの道のりは長い

今回はLispの基礎的な内容だけになってしまいましたが,かなり奇妙な言語で学んでいて非常に楽しかったので,Lisperに近づくために今後も書き続けてみようと思います!(また成長して記事にできたらいいなあ)

明日は同じくBackendのmotoakiの記事になります!お楽しみに!

参考資料

[1]「Land of Lisp」 Conrad Barski, M.D.著
[2] とほほのLISP入門 https://www.tohoho-web.com/ex/lisp.html#repl