Pythonで書かれたイカしたLisp: Hy

これは[iRidge Advent Calendar](http://qiita.com/advent-calendar/2016/iridge)の七日目の記事です。

この記事では、Pythonで実装されたLisp方言のHyを紹介します。

# 動機

株式会社アイリッジのサーバサイドエンジニア、tanaka.lispです。ウェブとPythonの世界を見てみたいと思い、Pythonの会社であるアイリッジでお仕事しています。

突然ですが、ぼくがこよなく愛する言語はCommon Lispです。業務ではもっぱらPythonを読んだり書いたりするわけですが、PythonはLispではないので、自動インデントに翻弄されたり、`if pred1 and pred2: ...`を`if (and pred1 pred2): ...`なんて書いてしまったりと苦労が絶えません。Lisper特有の禁断症状が顔を出すこともあります。S式のことを考え始めたり、おもむろに"Hyperspec `関数名`"でググったり、EmacsにS式を打ちこみ始めたり。果ては業務時間中に頭をよぎる__PythonがLispになればいいのに__という意味不明な考え──

ああ、PythonがS式だったら。[どこぞの言語](https://clojure.org/)のように、Pythonの膨大な標準ライブラリ群にタダ乗りしつつS式を書けたなら。ああ、ああ。

しかし、あるのです。そんな夢のような、イカした言語が。

# Hy

[Hy](http://docs.hylang.org/en/latest/)はPythonで書かれたLisp方言です。

Clojureの影響の強い構文を持ち、[Hy Style Guide](http://docs.hylang.org/en/latest/style-guide.html)にも、迷ったら`Python > Clojure > Common Lisp`の順でその言語の慣習に従え、とあるくらいです。

さらにはClojureよろしくPythonとのinteropが可能で、なんと[DjangoアプリをHyで書ける](https://github.com/paultag/djlisp/tree/master/djlisp)ようです。ああ、もうこれで、Hyでよくない？？

ちなみに[イカした言語といえばHaskellが有名でゲソ](http://www.paraiso-lang.org/ikmsm/books/c80.html)が、Hyのマスコットもイカのようです。イカしてますね。ドキュメントの[三分チュートリアル](http://docs.hylang.org/en/latest/quickstart.html)は最高に面白いので一読することをオススメします。ぼくはこれでノックアウトされました。

# Hyのインストール

それではインストールしていきましょう。ここではvirtualenv環境にインストールします[^1]。

[^1]: ちなみにubuntuのaptリポジトリに`python-hy`という名前で登録されているようです。

まずはvenv環境を作って、

```sh
$ virtualenv venv
# ...たくさんの出力
$ cd venv
$ source ./venv/bin/activate
```

次にpipでgithubからインストールします。

```sh
(venv) $ pip install git+https://github.com/hylang/hy.git
# ...たくさんの出力
```

あとはREPLを起動してこんにちは。

```sh
(venv) $ hy
hy 0.11.0+320.g5b87932 using CPython(default) 2.7.12 on Linux
=> (print "Hy!")
Hy!
=>
```

ね、簡単でしょう？

# Hyひとめぐり

では、どんな言語なのか見ていきます。

## 基本構文

### データ構造


```clojure
;;; 数値と真偽値
=> 42
42L
=> False
False
;;; 文字列
=> "forty two"
u'forty two'
```

まず、データ構造はPythonで使えるものがほぼそのまま使えます。が、`,`とか`:`とかデリミタを書くのは禁止らしいです。

```clojure
;;; リスト. `,`を書いてはダメ
=> ["life" "universe" "everything"]
[u'life', u'universe', u'everything']
;;; 辞書. これもデリミタ不要
=> {"arthor" "dent" "ford" "prefect"}
{u'arthor': u'dent', u'ford': u'prefect'}
```

そしてこちらはLisp特有のデータ型たち。シンボルの表示がUnicode文字列なのはなんとなく違和感ありますね。

```clojure
;;; シンボル. 表示は文字列だけど、型はちゃんとシンボル
=> 'marvin
u'marvin'
=> (type 'marvin)
<class 'hy.models.symbol.HySymbol'>
;; キーワード
=> :deep-thought
u'\ufdd0:deep-thought'
;; 他と常に非等値になるシンボル
=> (gensym)
u':G_1236'
```

そしてなんと、`nil`がない！　ああ、神よ、これではどうやって空リストを書けばいいのですか。

```clojure
;;; nilがない!
=> nil
Traceback (most recent call last):
  File "<input>", line 1, in <module>
NameError: name 'nil' is not defined
```

神「`()`を使えばいいじゃない！」  
ぼく「なるほど！」

```clojure
=> ()
[]
```

というか、Lispの伝統的なリスト記法でもPythonのリストになる**場合がある**ようです。

```clojure
=> '(a b c)
(u'a' u'b' u'c')
=> (cons 'googolplex (cons 'star (cons 'thinker ())))
[u'googolplex', u'star', u'thinker']
```

そしてコンス。Lispの父John McCarthyはコーンスと発音したらしいですね。

```clojure
=> (cons 'arther 'dent)
(u'arther' . u'dent')
;;; ドット対で書いてquoteしてもOK
=> '(arther . dent)
(u'arther' . u'dent')
```

### 関数

HyはLisp語族の言語なので、関数呼び出しはポーランド記法で書きます。

```clojure
;;; 文字列連結
=> (+ "forty" " " "two")
u'forty two'
;;; 四則演算
=> (+ 1 (* 5 8) 1)
42L
```

関数定義はClojureふうにこんな感じで。ついつい`defun`と入力して戸惑うあたりもいつもの感じです。

```clojure
=> (defn factorial [n]
...   (if (<= n 0)
...       1
...       (* n (factorial (- n 1)))))
=> (factorial 10)
3628800L
```

ところで、リストはコンスの糖衣構文です。Lispのコードはリストで書かれます。ということはコードをコンスで書くことだってできるはずです。Common Lispではドット対でプログラムを書いてもちゃんとreadされますが果たして…。

```clojure
=> (print . ("forty" . ("two" . ())))
forty two
```

おお！　やりますね！　これで難読化への道が開けました。

## Pythonの関数にアクセス

Pythonの豊富な標準ライブラリを利用する例として、[httplibを用いた通信の例](https://docs.python.org/2.7/library/httplib.html#examples)をHyでやってみます。

```clojure
;;; インポート. fromインポートもできます
=> (import httplib)

=> (setv conn (httplib.HTTPSConnection "www.python.org"))
=> (conn.request "GET" "/")
=> (setv res (conn.getresponse))
=> (print res.status res.reason)
200 OK
```

S式に読み替えるだけで、できてしまいました。Pythonで書いてたスクリプトをHyに移行するチャンスです。

## マクロ

さて、HyがただPythonに親しいだけではなく、ちゃんとLispの仲間であることも確認します。Lisperではない諸氏のために説明すると、ここでいうマクロとはCのマクロではなく、抽象構文木を操作するアレのことです。RustとかNimとか、Scalaなんかがこの機能を持っています。ちなみに、Hyはcontributor moduleとして[アナフォリックマクロを既に持っています](http://docs.hylang.org/en/latest/contrib/anaphoric.html)。

例として、とりあえずアナフォリックマクロなんて恩恵がわかりやすくて適当かなと思いました。アナフォリックマクロとは、たとえばif文の条件部分の式の値を後ろから参照できるようにするものですが、詳しくは[On Lispの該当の章](http://www.asahi-net.or.jp/~kc7k-nd/onlispjhtml/anaphoricMacros.html)でも読んでください。

とりあえずOn Lispからの引き写しで実装し、使ってみます。

```clojure
=> (defmacro aif [test-form then-form &optional else-form]
...  `(let [it ~test-form]
...     (if it ~then-form ,else-form)))
=> (aif (+ 1 2)
...  (print 'cheese it it))
cheese 3 3
```

`it`でif式 (ここでは`aif`) の条件式の評価結果を参照できています。式のほうがどのように変換されているかを見てみますと

```clojure
;;; 出力は見やすいよう手で整形
=> (macroexpand '(aif (+ 1 2)
...                (print 'cheese it it)))
((u'fn' [] (u'setv' u'it' (u'+' 1L 2L))
    (u'if' u'it'
        (u'print' (u'quote' u'cheese') u'it' u'it')
        u',else_form')))
```

シンボルがUnicode文字列で表示されてキモいですが、よさそうです。

こんなのの何が嬉しいのかというと

- 既に(条件分岐するときに)計算した値を利用できる
- 同じ条件式を何度も書かなくていい

というところが嬉しいポイントです。

例として、条件式が猛烈に時間の掛かる式の場合で考えてみます。

```clojure
=> (reduce (fn [a b] (+ a b)) (range 1 1000000000))
499999999500000000
```

この式、とりあえず打ってみたら**一分くらいREPLが沈黙してしまいました:sob:**。この結果を何度も使用したいときに、

```clojure
(aif (reduce (fn [a b] (+ a b)) (range 1 1000000000))
  (print (+ it it))
```

などとしておけば複数回の計算を避けることができるという寸法です。局所変数に代入しておけよという話ですが、アナフォリックマクロを書くと、裏で局所変数の作成と代入を自動でやってくれるのです。

# HyからPythonへアクセス

最後に。HyのコードをPythonコードに変換することができます。こうすることでREPLでもたもたするHyの危うさを解消したり~~開発するときはHyで書きコミットするときPythonに変換したり~~することができます。

やってみると、実際にパースするオーバーヘッドが減ったりするのか、結果が返ってくるまでのもたつきがなくなります。

```sh
(venv) $ cat upto.hy
(defn up-to-n [n]
  (list-comp n (n (range n)) (= (% n 2) 0)))

(print (up-to-n (integer (raw-input "input number: "))))

# upto.hyを変換
(venv) $ hy2py upto.hy > upto.py

# 変換結果
(venv) $ cat upto.py
from hy.core.language import integer, range

def up_to_n(n):
    return [n for n in range(n) if ((n % 2L) == 0L)]
print(up_to_n(integer(raw_input(u'input number: '))))
(venv) $ python upto.py
input number: 10
[0, 2, 4, 6, 8]

```


# まとめ

Pythonで書かれたLisp方言Hyを紹介しました。

- Hyのインストールは`pip`で簡単
- Hyの構文は簡単・便利・S式
- HyはPythonの資産にアクセスできる
- HyはLispのメタプログラミング力を持っている
- HyのコードをPythonのコードに変換して速くできる

結構しっかりした造りの言語だな、というのが触ってみて書いてみた感想です。

この記事は本来一年前に発信しようと考えていた[^2]ものでした。しかしというかやはりというか、Pythonの世界を知ってから触れるとHyのやりかたのバックグラウンドが推測できました。Pythonに馴染んだ今が丁度よい時期だった、ということなのかもしれません。

[^2]: 社内LTで発表しようと考えていましたが、この記事LTでやるの大変そうですね…。

Hy自体はなかなかいい言語だと思うので、Python業に従事するLisper諸兄におかれましては、「Pythonを使わないといけないけど構文がS式じゃないなあ」というようなときなどに導入を検討してみてはいかがでしょうか。