【Clojure】マクロを用いた関数の共通化

2023/04/24:更新(シンタックスクォートのみを用いた方式を紹介。それ以外を削除。)

内容がほとんど同じな2つの関数

java.timeLocalDateTimeZonedDateTimeparseメソッドを呼び出す2つの関数があります。 この2つの関数をマクロを用いて共通部分を抜き出したいです。

(ns code01
  (:import
   (java.time LocalDateTime
              ZonedDateTime)
   (java.time.format DateTimeFormatter)))

(defn parse-local
  ^LocalDateTime [^CharSequence text ^DateTimeFormatter formatter]
  (LocalDateTime/parse text formatter))

(defn parse-zoned
  ^ZonedDateTime [^CharSequence text ^DateTimeFormatter formatter]
  (ZonedDateTime/parse text formatter))

マクロの呼び出し例は以下になります。 マクロを呼び出すことで関数を定義します。

(def-parse parse-local LocalDateTime)
(def-parse parse-zoned ZonedDateTime)

シンタックスクォートによる定義

シンタックスクォートを用いたマクロ定義を下に示します。

(ns code02
  (:require
   [clojure.pprint])
  (:import
   (java.time LocalDateTime
              ZonedDateTime)
   (java.time.format DateTimeFormatter)))

(defmacro def-parse
  [fn-name java-class]
  `(defn ~fn-name
     [^CharSequence text# ^DateTimeFormatter formatter#]
     (. ~java-class ~'parse text# formatter#)))

(clojure.pprint/pprint (macroexpand-1 '(def-parse parse-local LocalDateTime)))

(def-parse parse-local LocalDateTime)
(def-parse parse-zoned ZonedDateTime)

実行結果は以下になります。

$ clojure -M src/code02.clj
(clojure.core/defn
 parse-local
 [text__146__auto__ formatter__147__auto__]
 (. LocalDateTime parse text__146__auto__ formatter__147__auto__))

シンタックスクォート中ではシンボルは名前空間名/シンボルに変換されます。 それを止めるには、クォートしたシンボルをアンクォートします。(例:`~'parse

また、マクロ中の関数の引数やlet内の変数などのローカル変数名は末尾に#をつけてマクロ展開時に他と被らない名前にします。 上の例ではtext#formatter##を使用しています。

これで無事にdefnするマクロを定義して処理を共通化できました。