2
1

More than 1 year has passed since last update.

Common Lisp 自作関数・マクロ紹介

Last updated at Posted at 2023-02-08

plot

(ql:quickload :plot/vega)

(defun plot (lst)
    (plot:plot
     (vega:defplot plot
       `(:data ,(plot-list-to-plist lst)
         :mark line
         :width 480
         :heigth 320
         :encoding (:x (:field :x
                        :type :quantitative)
                    :y (:field :y
                        :type :quantitative))))))

(defun plot-list-to-plist (lst)
  `(:x ,(make-array (length lst) :initial-contents (qutimes (i (length lst)) i))
    :y ,(make-array (length lst) :initial-contents lst)))

(plot '(1 2 3 4 5)) ; plotが表示される

pythonで言うところのmatplotlib.pyplotに相当する関数。plot/vegaというパッケージを利用。listをplistに変換しないといけない点に注意。シンプルなプロット以外にも、ヒストグラム等いろいろな種類がある。コード内のqutimesも自作マクロで、後述。
vegaパッケージ内で利用できるデータ数がコード内で指定されているため、大量のデータを扱うときにsetq等で扱えるデータ数を変更するということができない。(ql:where-is-system :plot)で返るディレクトリ配下の、src/vega/plot.lispの109行目あたりにある50000という定数を大きくすることを推奨 (例えば500000くらいに)。
py4clからpyplotの呼び出しも試したのだけれど、プロットウィンドウが閉じなくなる等の不具合があったので、利用を諦めた。

qutimes

(defmacro qutimes (count &body body)
  (let ((i (car count))
        (stop (cadr count))
        (acc (gensym)))
    `(labels ((rev (,i ,acc)
                (if (< ,i ,stop)
                    (rev (1+ ,i) (cons (progn ,@body) ,acc))
                    (reverse ,acc))))
       (rev 0 nil))))

(qutimes (i 5)
  (+1 i)) ; (1 2 3 4 5)

pythonのnp.arange(0, x, 1)のような挙動をするマクロ。与えられた数まで0から1つずつ値を取っていって何かしらの処理を行い、その返り値で構成されたリストを返す。上記の例で言えば、pythonの[1 + x for x in np.arange(0, 5, 1)]と同じ。
挙動がdotimesと似ていて、各ステップの返り値を溜め込むという要素がqueueっぽいので、qutimesという名前にした。

qulist

(defmacro qulist (lst &body body)
  (let ((symbol (car lst))
        (for (cadr lst))
        (ls (gensym))
        (acc (gensym)))
    `(labels ((rev (,ls ,acc)
                (if (consp ,ls)
                    (rev (cdr ,ls)
                         (let ((,symbol (car ,ls)))
                           (cons (progn ,@body) ,acc)))
                    (reverse ,acc))))
       (rev ,for nil))))

(qulist (elm '(1 2 3 4 5))
  (1+ elm)) ; (2 3 4 5 6)

pythonのリスト内包表記のような挙動をするマクロ。与えられたリストから1つずつ値を取っていって何かしらの処理を行い、その返り値で構成されたリストを返す。上記の例で言えば、pythonの[elm + 1 for elm in [1, 2, 3, 4, 5]]と同じ。
挙動がdolistと似ていて、かつリストの返り値を溜め込むという要素がqueueっぽいので、qulistという名前にした。

fstr

(ql:quickload :cl-ppcre)

(defmacro fstr (str)
  (labels ((peel (str) (subseq str 1 (1- (length str)))))
    (let ((symbols (mapcar #'(lambda (str)
                               (let ((val (read-from-string str)))
                                 (cond ((stringp val) str)
                                       ((symbolp val) val)
                                       (t val))))
                           (mapcar #'peel (cl-ppcre:all-matches-as-strings "{.*?}" str))))
          (format (cl-ppcre:regex-replace-all "{.*?}" str "~A")))
      `(format nil ,format ,@symbols))))

(let ((hoge "hOgE")
      (piyo 'pIyO)
      (fuga (+ 1 2)))
  (fstr "{hoge}/{piyo}/{fuga}")) ; hOgE/PIYO/3

pythonのf文字列のような挙動をするマクロ。シンボルが大文字で表示される点と、:を用いた制御ができない点には注意。

let-if

(defmacro let-if (test symbols bindings &body body)
  `(multiple-value-bind ,symbols
       (if ,test
           (values ,@(car bindings))
           (values ,@(cadr bindings)))
     ,@body))

(let-if (> 10 0) (x y) (("Plus" (+ 1 2)) ("Minus" 'hoge))
  (print x)
  (print y)) ; "Plus" 3

bindingsのcar部分に条件が真の場合の値を、cdar部分に条件が偽の場合の値を入れる。
python等の手続き型言語であれば

if 10 > 0:
    x = "Plus"
    y = 1 + 2
else:
    x = "Minus"
    y = "hoge"
print(x)
print(y)

のように一般的に書ける処理でも、Lispだと

(if (> 10 0)
  (let ((x "Plus")
        (y (+ 1 2)))
    (print x)
    (print y))
  (let ((x "Minus")
        (y 'hoge))
    (print x)
    (print y)))

のように、if分岐ごとにletで束縛して、それぞれに処理 ((print x) (print y)) を書かないといけない (はず)。このlet-ifを利用すれば、条件ごとの束縛を一括して記述できる。引数の渡し方に少しクセがあるのはご愛嬌。

let-case

(defmacro let-case (keyform symbols bindings &body body)
  `(multiple-value-bind ,symbols
       (case ,keyform
         ,@(qulist (bind bindings)
             `(,(car bind) (values ,@(cdr bind)))))
       ,@body))

(let ((hoge 10))
  (let-case hoge (x y) ((5 0 0) (10 100 100))
    (print (+ x y)))) ; 200

let-ifcase版。bindingsの各リストの最初の値が判定用。それに続く値が,@で分解・分配されて、束縛される。

let-cond

(defmacro let-cond (symbols bindings &body body)
  `(multiple-value-bind ,symbols
       (cond ,@(qulist (bind bindings)
                 `(,(car bind) (values ,@(cdr bind)))))
     ,@body))

(let-cond (x y) (((= 3 5) 3 (+ 2 3))
                 ((= 1 1) "Hoge" 'hoge))
  (list x y)) ; ("Hoge" HOGE)

let-ifcond版。bindingsの各リストの最初の値が判定用。それに続く値が,@で分解・分配されて、束縛される。

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1