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

  • 6
    いいね
  • 0
    コメント
この記事は最終更新日から1年以上が経過しています。

この記事は、Lispっぽいけど一応Python その2 Advent Calendar 2015の3日目の記事です。

前回に引き続きHyの可能性を探っていきます。

古いクラス定義方法

この記事の執筆時点の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ではドキュメントにある通り、クラスの定義方法が少し変わっています。

(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の

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

クラスを継承する

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

飼い猫を表す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

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

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の可能性を探りたいと思います。

この投稿は Python その2 Advent Calendar 20153日目の記事です。