KEMBAR78
Lispmeetup #53 PythonベースのLisp方言、 Hyのすすめ | PDF
PythonベースのLisp方言、
Hyのすすめ
Satoshi Imai
Twitter: @masatoi0
GitHub: masatoi
LispからPythonを利用する試み
● CLPython
– Common LispによるPythonの実装
– 開発停止中
● Pythonは言語仕様が標準化されていないので最新の実装に
追従し続けなければならない(つらい)
● burgled-batteries
– CPythonからCommon Lispへのブリッジ
Hyとは?
● Python上で動くLisp方言
– HyからPythonの機能を使える
– PythonからHyの機能を使える
● Clojureに似た構文を持つ
● マクロで拡張可能
● Hycc: 実行ファイル、共有ライブラリを生成できるコ
ンパイラ
なぜHyを使うのか?
● Pythonのライブラリを使いたい
● たまにしかPythonを使わないと構文を忘れる
– HyはClojureに近い構文なので連想が効く
● とにかくS式でプログラムを書きたい
● マクロを使いたい!
● デメリットとしては構文解析のオーバーヘッドが若干
ある
インストール
● Githubから最新版を入れる
● Hyccのインストール
$ pip install git+https://github.com/hylang/hy.git
$ pip install hycc
主なコマンド
● hy
– REPL起動または.hyファイル読み込み
● hyc
– Pythonのバイトコードにコンパイルする
● hy2py
– HyからPythonへトランスレートする
● hycc
– Cython経由で実行ファイル/共有ライブラリを作る
Emacsで使うとき: hy-mode
● package.elから入れられる
● .hyファイルを開いた状態でM-x inferior-lispで
REPLが起動する
● C-x C-eで式単位の評価ができたりする
● デバッガ、引数補完とかは無し
M-x package-install hy-mode
構文はClojureに似ている
● 関数定義
● 変数定義/代入
(defn fact [n]
"docstring"
(if (= n 0)
1
(* n (fact (- n 1)))))
(def x 10)
(setv y 20
z (* x y))
(print (.format "x: {0}, y: {1}, z: {2}" x y z))
;; x: 10, y: 20, z: 200
● オプショナル引数
● 可変長引数
(defn optional-arg [pos1 pos2 &optional keyword1 [keyword2 42]]
[pos1 pos2 keyword1 keyword2])
(optional-arg 'pos1 'pos2 'key1)
; => ['pos1', 'pos2', 'key1', 42]
(optional-arg 'pos1 'pos2 :keyword2 420)
; => ['pos1', 'pos2', None, 420]
(defn plus [&rest args]
(setv sum 0)
(for [i args]
(setv sum (+ sum i)))
sum)
(plus 1 2 3) ; => 6
● 無名関数はClojureと同様にfn
● PythonはLISP-1(関数と変数で名前空間が共通)
– Schemeっぽい関数定義も可能
((fn [one two three]
(print one two three))
1 2 3)
;; 1 2 3
(def fact2
(fn [n]
(if (= n 0)
1
(* n (fact2 (- n 1))))))
loop/recurマクロ
● 再帰呼び出しできるが末尾再帰最適化はしないの
で、Clojureと同様にloop/recurマクロが用意されて
いる
(require [hy.contrib.loop [*]])
(defn fact3 [n]
(loop [[cnt 1] [acc 1]]
(if (= cnt n)
acc
(recur (inc cnt) (* acc cnt)))))
その他の制御構造
● cond
● Pythonのfor
(setv val 20)
(cond [(> val 30) (print "too big")]
[(< val 10) (print "too small")]
[True (print "just size!")])
;; just size!
(for [x (range 0 10)]
(print x)
(if (= (% x 2) 0)
(continue))
(print "not even!"))
● Clojureのスレッドマクロ
● 例外処理
(+ (- (* 2 3) 4) 5) ; => 7
(-> (* 2 3) (- 4) (+ 5)) ; => 7
(+ 5 (- 4 (* 2 3))) ; => 3
(->> (* 2 3) (- 4) (+ 5)) ; => 3
(as-> (* 2 3) x (- x 4) (+ x 5)) ; => 7
(as-> (* 2 3) x (- 4 x) (+ x 5)) ; => 3
(try
(/ 2 0)
(except [e ZeroDivisionError] (print "Division by zero"))
(except [e TypeError] (print "Type Error"))
(else (print "no errors"))
(finally (print "all done")))
マクロ定義
● 伝統的マクロ
– バッククォートとアンクォート(~、~@)で組み立てる
– 変数捕捉の回避にはgensymが使える
– 展開結果の確認にはmacroexpandが使える
;; letはlambdaのシンタックスシュガー
(let ((one 1) (two 2) (three 3))
(print one two three))
((fn [one two three]
(print one two three))
1 2 3)
(defmacro let [var-pairs &rest body]
(setv var-names (list (map first var-pairs))
var-vals (list (map second var-pairs)))
`((fn [~@var-names] ~@body) ~@var-vals))
展開
データ構造
● リスト
'(1 2 3)
(type '(1 2 3)) ; <class 'hy.models.expression.HyExpression'>
;; 参照はnth
(nth '(1 2 3) 2) ; => 3
;; map/reduce/filter
(list (map inc (range 1 10)))
; => [2, 3, 4, 5, 6, 7, 8, 9, 10]
(reduce + (map inc (range 1 10)))
; => 54
(list (filter (fn [x] (= (% x 2) 0)) (range 1 10)))
; => [2, 4, 6, 8]
データ構造
● ディクショナリ
(setv dict {"dog" "bark" "cat" "meow"})
(type dict)
; => <type 'dict'>
;; 参照はgetメソッド
(.get dict "dog")
; => 'bark'
importとrequire
● importでHy/Pythonのパッケージを読み込む
● パッケージがHyのマクロを含む場合はマクロ展開の
ステップを挟む必要があるのでrequireを使う
(import sys
; 特定の関数だけをインポート
[os.path [exists
isdir :as dir?
isfile :as file?]]
; 別名をつけてインポート
[numpy :as np])
(require [hy.contrib.loop [*]]
[hy.extra.anaphoric [*]])
● パッケージを特定して呼び出し
(import os)
(.get os.environ "LC_CTYPE")
; => 'en_US.UTF-8'
(os.system "date")
; Thu Jun 29 15:20:38 JST 2017
; => 0
クラス定義
● selfの属性に代入することでスロットを作る
(defclass FooBar [object]
;; コンストラクタ
(defn --init-- [self x]
(setv self.x x))
;; アクセサ
(defn get-x [self]
self.x))
(setv obj (FooBar 1))
(.get-x obj) ; => 1
クラス定義
● クラスの継承
(defclass Dog [object]
(defn --init-- [self name]
(setv self.name name)))
(defclass SubDog [Dog]
(defn --init-- [self name type]
;; 親クラスの--init--メソッドを呼び出す
(.--init-- (super SubDog self) name)
(setv self.type type)))
(setv sd1 (SubDog "taro" "akita"))
(print sd1.name) ; => taro
(print sd1.type) ; => akita
NumPyで行列のかけ算をしてみる
(import [numpy :as np])
(def arr (np.array [[1 2 3]
[4 5 6]
[7 8 9]]))
arr.ndim ; => 2
arr.size ; => 9
arr.dtype ; => dtype('int64')
;; スカラー倍
(* arr 3)
;; 要素積
(* arr arr)
;; 行列積
(np.dot arr arr)
● timeモジュールで処理時間を測ってみる
(import time
[numpy.random :as rand])
;; 1000×1000の乱数行列をつくる
(def bigarr1 (rand.rand 1000 1000))
(def bigarr2 (rand.rand 1000 1000))
;; 行列積にかかる時間を計測
(do
(def start (time.time))
(np.dot bigarr1 bigarr2)
(def elapsed-time (- (time.time) start))
(print (+ (.format "Evaluation took:: {0}" elapsed-time) " seconds")))
; Evaluation took:: 0.07291960716247559 seconds
timeitマクロを定義する
(defmacro timeit [&rest body]
(setv start (gensym)
end (gensym))
`(do
(setv ~start (time.time))
~@body
(setv ~end (time.time))
(print (+ (.format "Evaluation took: {0}" (- ~end ~start))
" seconds"))))
(timeit (np.dot bigarr1 bigarr2))
; Evaluation took:: 0.07291960716247559 seconds
Kerasでニューラルネットのモデルを定義してみる
(def model (Sequential))
(.add model (Dense :input-dim 784 :units 512))
(.add model (Activation "relu"))
(.add model (Dropout 0.2))
(.add model (Dense :units 512))
(.add model (Activation "relu"))
(.add model (Dropout 0.2))
(.add model (Dense :units 10))
(.add model (Activation "softmax"))
(.compile model
:loss "categorical_crossentropy"
:optimizer (Adam)
:metrics ["accuracy"])
(define-sequential model
[(Dense :input-dim 784 :units 512)
(Activation "relu")
(Dropout 0.2)
(Dense :units 512)
(Activation "relu")
(Dropout 0.2)
(Dense :units 10)
(Activation "softmax")]
{:loss "categorical_crossentropy"
:optimizer (Adam)
:metrics ["accuracy"]})
PythonからHyを呼ぶ
● Hyで書かれたモジュールを読み込む前にHy自体
をimportしておく
import hy
import greetings
greetings.greet("Foo")
# Hello from hy, Foo
print(greetings.THIS_WILL_BE_IN_CAPS_AND_UNDERSCORES)
# See?
(setv *this-will-be-in-caps-and-underscores* "See?")
(defn greet [name]
(print "hello from hy," name))
greetings.hy
人気のインフラに乗る
● ESR: Lispで悟りが得られるけど実際に使うことは
ないよ
– PG: コンピュータに読めればいいんで言語はプログラ
マの自由だよ
● そうはいってもライブラリ少ないよ
– →人気のインフラに乗るよ
● Clojure(JVM)
● Clojurescript(Javascript)
● Clasp(LLVM)
● Hy(Python)
まとめ
● HyはPython上に作られたLisp方言
● 構文はほとんどClojure
● Pythonのライブラリを簡単に使えるし、Hyで書いた
ものをPythonから使うのも簡単
● 良くも悪くも薄いトランスレータなのでPythonの知
識は必要
● いざとなったらマクロで構文を拡張できる安心感!

Lispmeetup #53 PythonベースのLisp方言、 Hyのすすめ