Hyに入門してみた・クラスを定義する

この記事は、Lispっぽいけど一応[Python その2 Advent Calendar 2015](http://qiita.com/advent-calendar/2015/yet_another_python)の3日目の記事です。

[前回](http://qiita.com/csakatoku/items/444db2d0e421265ec106)に引き続き[Hy](http://hylang.org/)の可能性を探っていきます。

## 古いクラス定義方法
この記事の執筆時点のHyの安定版は0.11.0です。

0.11.0ではクラスを定義する場合には`defclass`を使って以下のように書きます。

```
(defclass FooBar [object]
  [[--init--
    (fn [self x]
      (setv self.x x)
      None)]

   [get-x
    (fn [self]
      "Return our copy of x"
      self.x)]])
```

## 新しいクラスの定義方法
最新の開発版のHyでは[ドキュメントにある通り](http://docs.hylang.org/en/latest/tutorial.html#basic-intro-to-lisp-for-pythonistas)、クラスの定義方法が少し変わっています。

```
(defclass FooBar [object]
  (defn --init-- [self x]
    (setv self.x x))

  (defn get-x [self]
    "Return our copy of x"
    self.x))
```

0.11.0以前に比べて、すっきりした書き方になっていますね。

この書き方ができるバージョンがいつ安定版となるかは分からないのですが、どうせ変わるならば新しい書き方に慣れておこうと思うので、以降はgithubからインストールした開発版のHyを使うことを前提として書きます。

```
pip install git+https://github.com/hylang/hy.git
```

## `if __name__ == '__main__'`のHyでの書き方
クラス定義とは関係がありませんが、動作確認用のスクリプトを書いていると、Pythonの

```py
if __name__ == '__main__':
    # ここで何かする
    func()
```

に相当するイディオムをHyでも書きたくなります。

どうやらPythonの`__X__`はHyでは`--X--`になるらしいので、以下のように書けます。

```
; hello.hy
(defn hello []
  (print "Hello, World"))

(if (= --name-- "__main__")
  (hello))
```

`hy`インタプリタで実行。

```
$ hy ~/tmp/hello.hy
Hello, World
```

## クラスを継承する
いざクラス継承を使ったオブジェクト指向プログラミングの例を考えるとなると、有用な例がぱっと思い浮かばなかったので、[不朽の名作『ルドルフとイッパイアッテナ』](http://www.amazon.co.jp/dp/4061335057/)を題材にして、トリビアルなサンプルを作ってみました。

飼い猫を表す`Cat`クラスと野良猫を表す`StrayCat`を抽象クラス`AbstractCat`から継承して定義します。

```
; cat.hy
(defclass AbstractCat [object]
  (defn --init-- [self name]
    (setv self.name name))

  (defn greet [self]
    (raise NotImplementedError)))

(defclass StrayCat [AbstractCat]
  (defn greet [self]
    (.format "俺の名前は{}" self.name)))

(defclass Cat [AbstractCat]
  (defn --init-- [self name home]
    (.--init-- (super Cat self) name)
    (setv self.home home))

  (defn greet [self]
    (.format "僕の名前は{}。{}に住んでいます。" self.name self.home)))

(defn main []
  (let [cat1 (Cat "ルドルフ" "りえちゃんの家")
        cat2 (StrayCat "イッパイアッテナ")]
    (print (.greet cat1))
    (print (.greet cat2))))

(if (= --name-- "__main__")
  (main))
```

`__X__`が`--X--`になるというだけで、Pythonの構文をほぼLispの構文に移していっただけという印象をうけますね。

コンストラクタ`__init__`は`--init--`になり、プロパティをセットする場合は`(setv self.key value)`を使います。

```
(defclass AbstractCat [object]
  (defn --init-- [self name]
    (setv self.name name))
```

親クラスのメソッドを呼び出す、Pythonの`super(X, self).method()`イディオムは、Lispの構文に置き換えただけで、ほぼそのままです。上の例では、親クラスのコンストラクタを呼び出す際に使っています。

```
(defclass Cat [AbstractCat]
  (defn --init-- [self name home]
    (.--init-- (super Cat self) name)
    (setv self.home home))
```

抽象メソッドを表すため`NotImplementedError`例外を投げるパターンも、`(raise NotImplementedError)`とほぼそのまま移植できます。

```
(defclass AbstractCat [object]
  ;; 略
  (defn greet [self]
    (raise NotImplementedError)))
```

## hy2pyを使ってHyコードをPythonコードに変換する
Hyには`hy2py`というHy→Pythonの変換ツールが付属しています。このツールを使って上の例をPythonコードに変換してみます。

```
hy2py cat.hy
```

結果は以下のようになります。

```py
from hy.core.language import name


class AbstractCat(object):

    def __init__(self, name):
        self.name = name
        self.name
        return None

    def greet(self):
        raise NotImplementedError


class StrayCat(AbstractCat):

    def greet(self):
        return u'\u4ffa\u306e\u540d\u524d\u306f{}'.format(self.name)


class Cat(AbstractCat):

    def __init__(self, name, home):
        super(Cat, self).__init__(name)
        self.home = home
        self.home
        return None

    def greet(self):
        return u'\u50d5\u306e\u540d\u524d\u306f{}\u3002{}\u306b\u4f4f\u3093\u3067\u3044\u307e\u3059\u3002'.format(self.name, self.home)

def main():

    def _hy_anon_fn_6():
        cat1 = Cat(u'\u30eb\u30c9\u30eb\u30d5', u'\u308a\u3048\u3061\u3083\u3093\u306e\u5bb6')
        cat2 = StrayCat(u'\u30a4\u30c3\u30d1\u30a4\u30a2\u30c3\u30c6\u30ca')
        (cat1, cat2)
        print(cat1.greet())
        return print(cat2.greet())
    return _hy_anon_fn_6()
(main() if (__name__ == u'__main__') else None)
```

意味のない`return None`が入っている以外は、ほぼ意図したとおりのPythonコードに変換されました。

## 最後に
Hyの最新開発版を使えば、`defclass`で非常に直観的にクラスを定義できるので、あまり難しさを感じさせません。

Pythonの文法が単純なためか、PythonコードとHyコードは非常に似通って見えて、「すべてのプログラミング言語はLispに至る」ということを実感させますね。

次回はより大きなプログラムを書いてHyの可能性を探りたいと思います。