すなぶろ

フリーランスのWebエンジニアがPythonを中心にプログラミング情報を紹介していきます。Rubyも書きますが、今はPython勉強中。一緒にPythonista目指しましょう!

Hy の macroexpand が S 式じゃない→直した

Python へ直接コンパイルできる Lisp 方言 Hy が楽しいです。 しかし Lisp 族のつもりでいると思わぬ落とし穴があったり( let, cons がないなど)するので、 とりあえず macroexpand の結果を S 式にしてみます。

例えば let をマクロで定義してみましょう。 let の正体は lambda というか関数なので、 無名関数を使って実装します。

(defmacro let [values &rest body]
  (setv var-names (list (map first  values))
        var-vals  (list (map second values)))
  `((fn [~@var-names] ~@body) ~@var-vals))

デフォルトの macroexpand-1 の挙動はこんな感じ。

=> (macroexpand-1 '(let ((var 1)) (print var)))
HyExpression([
  HyExpression([
    HySymbol('fn'),
    HyList([
      HySymbol('var')]),
    HyExpression([
      HySymbol('print'),
      HySymbol('var')])]),
  HyInteger(1)])

待って読めない。

もうちょっと Lisper 向けの表現(= S-expression)に近づけてほしいので、 hy コマンドを起動するときに引数をつけてあげます。

  • hy --repl-output-fn=hy.contrib.hy-repr.hy-repr

これで macroexpand してみると、今度はこんな感じに。

=> (macroexpand-1 '(let ((var 1)) (print var)))
'((fn [var] (print var)) 1)

なぜか一行で表示されましたが、それでもこちらのほうが何倍も読みやすいです。

私は Emacs 使いなので hy-mode 経由で hy シェルを起動しているんですが、その場合は hy-shell-interpreter-args (デフォルト "--spy" )を変更すればよい感じです。

(add-hook 'hy-mode-hook
          (lambda ()
            (setq hy-shell-interpreter-args
                  (concat "--repl-output-fn=hy.contrib.hy-repr.hy-repr "
                          hy-shell-interpreter-args))))

Hy がもっと繁栄しますように。