プログラミングにおいて、特定の問題領域に特化した独自の言語(DSL: Domain Specific Language)を作成したいことがあります。例えば:
Clojureは、このような独自言語の作成が特に得意な言語です。その理由を見ていきましょう。
Clojureでは、コードもデータも同じリスト形式で表現します:
;; データとしてのリスト
(def 買い物リスト '(りんご みかん バナナ))
;; コードとしてのリスト
(+ 1 2 3)
これは、Javaなどの一般的な言語とは大きく異なります:
マクロを使うと、Clojureの文法を拡張して独自の表現を作れます。これは、Excelで数式を定義するようなイメージです:
;; 通常のif文
(if (> x 0)
(println "正の数")
(println "負の数"))
;; マクロで日本語的な表現を定義
(もし (> x 0)
(なら (println "正の数"))
(でなければ (println "負の数")))
基本的な構文は以下の通りです:
(defmacro マクロ名 [引数ベクタ] 本体)
これは、Javaのメソッド定義に似ていますが、重要な違いがあります:
;; 基本的な引数
(defmacro 足す [x y]
`(+ ~x ~y))
;; 可変長引数(&を使用)
(defmacro 全部足す [最初 & 残り]
`(+ ~最初 ~@残り))
Javaの可変長引数(...)と似ていますが、より柔軟です。
マクロでコードを生成する際、テンプレートのような機能が使えます。これは、Javaの文字列テンプレートやExcelの数式テンプレートに似ています:
`(バッククォート):コードのテンプレート
;; Javaの例:
String template = "Hello, " + name + "!";
;; Clojureの例:
`(str "Hello, " ~name "!")
~(チルダ):値の埋め込み
;; Excelの例:
="合計: " & A1
;; Clojureの例: `(str "合計: " ~値)
~@(チルダアット):リストの展開
;; Javaの例:
String[] args = {"x", "y"};
method(1, ...args);
;; Clojureの例: `(関数 1 ~@引数リスト)
より自然な日本語での条件分岐を実現するマクロを作ってみましょう:
(defmacro もし [条件 & 本体]
`(if ~条件
(do ~@本体)))
;; 使用例
(もし (> 点数 80)
(println "優秀です!")
(println "おめでとう!"))
;; 展開結果
(if (> 点数 80)
(do
(println "優秀です!")
(println "おめでとう!")))
マクロのデバッグには専用の関数があります:
;; 1段階の展開
(macroexpand-1 '(もし true (println "テスト")))
;; 完全な展開
(macroexpand '(もし true (println "テスト")))
これは、Excelで数式の計算過程を確認するのに似ています。
Clojureのマクロシステムを使えば、既存の言語機能を拡張して、より自然で理解しやすい独自の表現を作ることができます。これは、各開発者が自分の理解しやすい方法でコードを書けることを意味します。
マクロの学習は最初は難しく感じるかもしれませんが、ExcelやJavaなどの身近な例と比較しながら理解を深めていくことで、その強力な機能を活用できるようになります。