はじめに
PythonistaのためのLisp入門(Hyチュートリアル和訳)の補足的な記事です.Hy Tutorialでは**「Pythonでこう書くものはHyではこう書く」的な内容がほとんどでしたがそれも実用には十分でないところが有ります.この記事ではHyならではの,言い換えるとPythonにはないHyの機能およびHy独自の特徴**について補足的に解説したいと思います.これを読めばとりあえずPythonの代わりにHyを使うには十分だと思います.
あわせて読みたい
Qiita以外だと以下の良記事を見つけたので併せてご参照ください.(前の投稿とモロかぶりのうえこちらのほうが有用です...)
型
Pythonと同様の型をサポートしています.ただし,Python2.x系と3.x系で異なる部分が有ります.挙動は同じですが型が異なります.
整数型
Python2.xではlong
,3.x系ではint
となります.
=> (type 2)
<type 'long'>
=> (type 2)
<class 'int'>
文字列型
Python2.xではunicode
,3.x系ではstr
となります.
=> (type "a")
<type 'unicode'>
=> (type "a")
<class 'str'>
ちなみに文字列はダブルクォーテーション("
)で括ります.クォーテーション('
)はダメです.また,Pythonと異なり,途中での改行が認められています.
nil
HyはLispなのでnilがないと始まらないように思います.しかしHyはPythonでもあるのでどちらかというと型に関してはPython準拠です.従ってnilはありません.
文脈に応じて空のリスト([]
or ()
),空のタプル((,)
),False
,None
などを用いましょう.LisperでないPythonistaな方は今までどおりで大丈夫です.
括弧の種類
Clojureと同様,他のLispたちとは異なり括弧に区別が有ります.
(...)
リストを表します.ただし,Pythonのlist
とは微妙に異なる概念ですので区別のためにLispのリストと呼ぶことにします.
Lispのリストでは最初の要素を関数とし,残りの要素をその引数として適用します.
従って(function value1 value2 ...)
はPythonでいうfunction(*[value1, value2 ...])
に相当します.
=> (+ 1 2 3)
6
=> (1 2 3)
Traceback (most recent call last):
File "/usr/local/lib/python3.5/dist-packages/hy/cmdline.py", line 102, in runsource
ast_callback)
File "/usr/local/lib/python3.5/dist-packages/hy/importer.py", line 176, in hy_eval
return eval(ast_compile(expr, "<eval>", "eval"), namespace)
File "<eval>", line 1, in <module>
TypeError: 'int' object is not callable
二番目の例ではPythonでいう1(*[2, 3])
を評価したこととなり,もちろん1
はcallable
では無いのでエラーとなります.
関数としての評価をしたくない時にはクォーテーション('
)を用います.
=> '(1 2 3)
(1 2 3)
[...]
Pythonにおけるいわゆるリストを表します.またClojureに習ってベクタとも呼びます.これは先程のクォートしたLispのリストと等価です.
=> [1 2 3]
[1, 2, 3]
=> (= [1 2 3] '(1 2 3))
True
{...}
Pythonと同じく辞書(dict
)をあわらします.キーと要素を交互に並べます.
=> {"a" 1 "b" 2}
{'b': 2, 'a': 1}
またキーは:key
の形でも良いです.
=> {:a 1 :b 2}
{'\ufdd0:a': 1, '\ufdd0:b': 2}
要素のアクセスはget
を用います.
=> (get {"a" 1 "b" 2} "a")
1
=> (get {:a 1 :b 2} :a)
1
また,その2の書き方(:key
)の場合は以下のアクセスも可能です.
=> (get {:a 1 :b 2} :a)
1
=> (:a {:a 1 :b 2})
1
まとめ
ここまでを表にまとめます.また説明していませんでしたがタプルも表に含めています.
Hyでの表現 | 対応するPythonでの表現 |
---|---|
リスト (function arg1 arg2 ...)
|
関数の呼び出し function(*[args1, arg2, ...])
|
クォートしたリスト '(elem1 elem2 ...)
|
リスト [elem1, elem2, ...]
|
ベクタ [elem1 elem2 ...]
|
リスト [elem1, elem2, ...]
|
タプル (, elem1 elem2 ...)
|
タプル (elem1, elem2, ..., )
|
辞書その1 {"key1" elem1 "key2" elem2 ...}
|
辞書 {"key1": elem1, "key2": elem2, ...}
|
辞書その2 {:key1 elem1 :key2 elem2 ...}
|
辞書 {"key1": elem1, "key2": elem2, ...}
|
関数のオーバーロード(多重定義)
PythonではC++やJavaのように引数の型や個数に拠って関数の本体を切り替えることはできません.Hyではhy.contrib
以下のモジュールにこれを実現するマクロが用意されています.
hy.contrib.multi.defn
組み込みのマクロであるdefn
を拡張して引数の個数による多態を実現します.
(require [hy.contrib.multi [defn]])
(defn function
([a] (+ "a = " (str a)))
([a b] (+ "a = " (str a) ", b = " (str b))))
(print (function 3))
;; > a = 3
(print (function 3 4))
;; a = 3, b = 4
上記の例では引数の個数がひとつの場合とふたつの場合で処理を切り替えています.なお,通常のdefn
と同じように使えばそのまま問題なく使えるので安心してください.
hy.contrib.multi.defmulti, defmethod, default-method
多重ディスパッチによる特殊化を実現します.いわゆるマルチメソッドと呼ばれるものです.
(require [hy.contrib.multi [defmulti defmethod default-method]])
(defmulti add [x y] (, (type x) (type y)))
(defmethod add (, int str) [x y]
(+ (str x) y))
(defmethod add (, str int) [x y]
(+ x (str y)))
(default-method add [x y]
(+ x y))
(print (add 1 "st"))
;; > 1st
(print (add "FF" 14))
;; > FF14
(print (add 2 4))
;; > 6
(print (add "Hello, " "world!"))
;; > "Hello, world!"
defmulti
マクロでは引数の条件に用いる要素を定義します.今回の場合だと,2つの引数をとってその引数の型を格納したタプル(, (type x) (type y))
をトリガーとしてディスパッチしています.defmethod
マクロでは条件を設定し,条件ごとの実行内容を定義します.またdefault-method
マクロはどの条件にも当てはまらなかった場合の実行内容を定義できます.上記の例では型をトリガーにしていますが,ぶっちゃけ何でもトリガーにできます.
例えば以下のようなコードも大丈夫です.
(require [hy.contrib.multi [defmulti defmethod default-method]])
(defmulti funtion [&rest args] (first args))
(defmethod funtion 1 [&rest args]
(print "the list of arguments starts with 1"))
(defmethod funtion 2 [&rest args]
(print "the list of arguments starts with 2"))
(default-method funtion [&rest args]
(print "the list of arguments starts with something other than 1 and 2"))
(funtion 1 3 4)
;; > the list of arguments starts with 1
(funtion 2 3)
;; > the list of arguments starts with 2
(funtion 4 8 9 0)
;; > the list of arguments starts with something other than 1 and 2
上記の例では複数個の引数を受け取ってその先頭要素が何かでディスパッチしています.
遅延シーケンス
Pythonのジェネレータのようなものですが,ジェネレータとは異なり同じ要素に何度でもアクセスできます.もちろん,ジェネレータ同様遅延評価されます.要は,遅延評価+メモ化が備わったリストのようなものです.hy.contrib.sequences.defseq
マクロで定義できます.例えばフィボナッチが以下のように書けます.
(require [hy.contrib.sequences [defseq]])
(import [hy.contrib.sequences [Sequence]])
;; マクロ展開後に使用されているのでimportしておかいといけない
(defseq fibonacci [n]
(if (<= n 1) n
(+ (get fibonacci (- n 1)) (get fibonacci (- n 2)))))
(print (get fibonacci 100))
;; > 354224848179261915075
上記のコードは遅延評価のおかげでミリ秒で終了しますが,普通に再帰関数で書くと恐ろしく時間がかかります.
先行ドット構文(2017/06/06追記)
Hyではメソッド呼び出しに先行ドット構文(leading dot syntax)というものが使えます.(object.method args...)
を(.method object args...)
のように書けるシンタックスシュガーです.
=> (def a [1 2 3])
=> (.append a 4)
=> a
[1, 2, 3, 4]
=> (import ast)
=> (.parse ast "print(\"Hello, world!\")" :mode "eval")
<_ast.Expression object at 0xb6a2daec>
スレッドマクロ(2017/06/06追記)
チュートリアルの最後にも出てきましたが,Hyにはスレッドマクロという可読性を向上させる非常に便利な機能があります.これはClojureから引き継いだものです.いくつか種類が有りますので個別に説明します.
->, ->>
(func3 (func2 (func1)))
を(-> func1 func2 func3)
のように書けます.->
の場合次の式の最初の引数としてチェーンされ,->>
の場合最後の引数としてチェーンされます.具体例を示します.
=> (def a 8.)
=> (-> a (/ 4) (- 1)) ;; (- (/ a 4) 1)
1.0
=> (->> a (/ 4) (- 1)) ;; (- 1 (/ 4 a))
0.5
as->
->
および->>
では最初か最後にしか引数を渡せません.途中に渡したい場合や,関数によって渡す位置が変わる場合に対応できません.そこで活躍するのがas->
です.
=> (as-> a it
... (/ it 4)
... (- 1 it))
-1.0
ここではa
にit
という一時的な名前をつけています.
doto
すこし毛色が異なりますがまとめて紹介します.D言語のWith構文のようなもので,単一のオブジェクトに対するメソッド呼び出しの連続を簡単にするものです.(obj.method1) (obj.method2) ...
を(doto obj .method1 .method2 ...)
のように書けます.
=> (doto [2 3] (.append 1) .sort)
[1, 2, 3]
スレッドマクロの使いどころ
括弧のネストが深くなる場合に特に有効です.Pythonではイマイチになってしまう表現をスマートに書ける場合も多く有ります."tanaka taro"
を"Taro Tanaka"
に変換する例を考えてみましょう.
" ".join(map(str.capitalize, reversed("tanaka taro".split())))
処理の流れを追うのが少し面倒です.これと同様のことをHyでは以下のようにできます.
(->> "tanaka taro" .split reversed (map str.capitalize) (.join " "))
非常にすっきりした見た目であるだけでなく,処理の流れも左から右へスラスラ読めるようになりました.
要するにスレッドマクロとはRubyのメソッドチェーンやD言語のUFCSのようなものです.可読性が格段に向上しますので積極的に使いましょう.
おわりに
その他思いつけば追記すると思います.また間違い等あればぜひご指摘ください.