この記事は、Lispっぽいけど一応Python その2 Advent Calendar 2015の3日目の記事です。
古いクラス定義方法
この記事の執筆時点の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の可能性を探りたいと思います。