M.Hiroi's Home Page

Python3 Programming

Hylang Programming

[ Home | Light | Python3 ]

WHAT'S NEW

CONTENTS

お気楽 Halyng プログラミング超入門


はじめに

Hy (または Hylang) は Python で動作する Lisp 方言のひとつです。2013 年の PyCon (Python Conference) で Paul Tagliamonte 氏が発表したオープンソース・ソフトウェア (MIT ライセンス) です。現在 (2024 年 4 月) のバージョンは 0.28 です。

プログラムを S 式 (リスト) で記述するところは Lisp (Clojure とよく似ている) なのですが、データ構造は Python のものを使うことを前提としているようで、Lisp の cons, car, cdr に相当する関数がないところがユニークです。そのかわり、Python の機能 (ライブラリ) は簡単に利用することができますし、逆に Python から Hy のプログラムを呼び出すこともできるようです。もちろん、Common Lisp や Clojure のような「マクロ」も用意されています。

Hy は pip で簡単にインストールすることができます。

python3 -m pip install hy

Hy を実行するときはシェルで hy と入力してください。

$ hy
Hy 0.28.0 using CPython(main) 3.10.12 on Linux
=> (print "hello, Hylang!")
hello, Hylang!

メッセージとプロンプト '=>' が表示され、対話モード (REPL, Read-Eval-Print-Loop) で Hy が起動されます。終了するときは (quit) または CTRL-D を入力してください。

Hy の関数呼び出しは Lisp (Clojure) と同じです。

(関数 引数 ...)

カッコの先頭が関数で、残りの要素が引数になります。引数は左から順番に評価され、その結果が関数に渡されます。Python の関数も同じ形式で呼び出すことができます。print は引数を画面に表示する Python の関数です。

ソースファイルを実行するときもコマンド hy を使います。

$ hy source_file.hy

Hy の場合、ソースファイルの拡張子は .hy になります。

リスト : hello.hy

(print "hello, Hylang!")
$ hy hello.py
hello, Hylang!

$

Hy はプログラムを Python のバイトコードにコンパイルし、それを Python の仮想マシンで実行します。Hy のプログラムをバイトコードにコンパイルするコマンド hyc や、Hy のプログラムを Python のプログラムに変換するコマンド hy2py も用意されています。

$ hyc hello.hy
Compiling 'hello.hy' --> '__pycache__/hello.cpython-310.pyc'
$ python3 __pycache__/hello.cpython-310.pyc
hello, world

$ hy2py hello.hy
import hy
print('hello, world\n')

本ページは Hy ver 0.28 を使って簡単なプログラムを作ってみようと思います。バージョンからわかるように、Hy は開発途上のプログラミング言語です。今後、言語仕様が大きく変更され、作成したプログラムが動かなくなることもあると思います。その際はご容赦くださいませ。


参考 URL


本ページ (Hylang Programming) の著作権は筆者「広井誠 (Makoto Hiroi)」が保持します。無断使用や無断転載は禁止いたします。本ページで作成したプログラムはフリーソフトウェアとします。ご自由にお使いください。プログラムの改造や配布もご自由にどうぞ。その際は、出典を明記してくださるようお願いいたします。

なお、これらのドキュメントやプログラムは無保証であり、使用したことにより生じた損害について、作者「広井誠 (Makoto Hiroi)」は一切の責任を負いません。また、これらのプログラムを販売することで利益を得るといった商行為は禁止いたします。

Copyright (C) 2024 Makoto Hiroi
All rights reserved.

お気楽 Hylang プログラミング超入門

●データ型

Hy のプログラムは Lisp と同じく S 式で記述します。Hy はプログラムを読み込んで S 式に変換し、それを Python のバイトコードにコンパイルして実行します。S 式はリスト (linked list) とアトム (atom) に分けることができます。リストは Lisp と同じくカッコ ( ) で囲んで表します。リスト以外のデータ型がアトムになります。

Hy のリストはプログラムを記述するために使用します。データとしてリストを操作することもできますが、それは主にマクロを使用するときに行われます。データ構造は Python のものを利用するのが Hy のプログラミングスタイルのようです。ここが Lisp (Clojure) とは大きく異なる Hy の特徴だと思います。

Hy と Python の主なデータ型の対応を以下に示します。

 Hy          Python        Type
------------------------------------
 1           1             int
 1.2         1.2           float
 1+2j        1+2j          complex
 True        True          bool
 "foo"       'foo'         str
 b"bar"      b'bar'        bytes
 #(1 2 3)    (1, 2, 3)     tuple
 [1 2 3]     [1, 2, 3]     list
 #{1 2 3}    {1, 2, 3}     set
 {1 2  3 4}  {1: 2, 3: 4}  dict

Hy の場合、コレクション (タプル、リスト、セット、ディクショナリ) の要素は空白で区切ることに注意してください。

●算術演算

算術演算を行う関数は Python の算術演算子と同じです。

+, -, *, **, //, /, %
(+ 1 2)   => 3
(- 1 2)   => -1
(- 1)     => -1
(* 3 2)   => 6
(// 3 2)  => 1
(/ 3)     => 0.3333333333333333
(/ 3 2)   => 1.5
(% 3 2)   => 1
(** 2 10) => 1024

変数に値をセットすることを「代入」といいます。Common Lisp は setq や setf を使いますが、Hy では setv を使います。

(setv 変数名 値)
=> (setv a 10)
=> a
10
=> (setv b 20)
=> b
20
=> (setv c (+ a b))
=> c
30

Lisp と違って、setv は値を返しません。値を返したい場合は setx を使います。

=> (setx d (* a b c))
6000
=> d
6000

タプルやリストを使って多重代入することもできます。

=> (setv #(a b) #(b a))
=> a
20
=> b
10
=> (setv [a b c] [1 2 3])
=> a
1
=> b
2
=> c
3

●コレクションの操作

Hy の場合、シーケンス (タプル、リスト、文字列など) やディクショナリの要素は関数 get でアクセスします。リストとディクショナリは get と setv で要素を書き換えます。

(get collection index-or-key)
(setv (get collection index-or-key) item)
=> (setv a [1 2 3 4 5])
=> (get a 0)
1
=> (get a 4)
5
=> (setv (get a 2) 30)
=> a
[1 2 30 4 5]
=> (get #(1 2 3 4 5) 1)
2
=> (get #(1 2 3 4 5) 3)
4
=> (setv b {"foo" 1 "bar" 2 "baz" 3})
=> b
{"foo" 1  "bar" 2  "baz" 3}
=> (get b "foo")
1
=> (get b "baz")
3
=> (setv (get b "bar") 20)
=> b
{"foo" 1  "bar" 20  "baz" 3}

要素の有無は関数 in, not-in で調べることができます。

=> a
[1 2 30 4 5]
=> (in 30 a)
True
=> (in 3 a)
False
=> (not-in 3 a)
True

=> b
{"foo" 1  "bar" 20  "baz" 3}
=> (in "bar" b)
True
=> (not-in "bar" b)
False
=> (not-in "oops" b)
True

=> (in 3 #(1 2 3 4 5))
True
=> (in 30 #(1 2 3 4 5))
False
=> (not-in 30 #(1 2 3 4 5))
True

=> (in 3 #{1 2 3 4 5})
True
=> (in 30 #{1 2 3 4 5})
False
=> (not-in 30 #{1 2 3 4 5})
True

Python のメソッドは object.method(args, ...) という形式で呼び出しますが、Hy では次の形式でメソッドを呼び出します。

(.method object args ...)
=> (setv a [1 2 3 4 5])
=> a
[1 2 3 4 5]
=> (.append a 10)
=> a
[1 2 3 4 5 10]
=> (.pop a)
10
=> a
[1 2 3 4 5]
=> (.insert a 0 100)
=> a
[100 1 2 3 4 5]
=> (.remove a 100)
=> a
[1 2 3 4 5]

スライスはシーケンスの部分列を取り出す操作です。Hy の場合、スライスは cut を使います。

(cut seq [start] [end] [step])

Python との対応を以下に示します。

Hy                 Python        意味
-------------------------------------------------------
(cut S start end)  S[start:end]  start から end - 1 まで
(cut S start)      S[start:]     start から最後尾まで
(cut S None end)   S[:end]       先頭から end - 1 まで
(cut S)            S[:]          先頭から最後尾まで

S はシーケンスを表します。スライスによって取り出された部分列は元のシーケンスをコピーしたものです。start と end に部分列を指定します。start の位置の要素が部分列の先頭になり、end - 1 の要素が最後尾になります。start が省略されると先頭 (0) に、end が省略されるとシーケンスの要素数に設定されます。どちらも省略するとシーケンスをコピーすることになります。

なお、Hy の (cut S n) は (cut S None n) と同じ動作になります。つまり、先頭から n 個の要素をコピーすることになります。また、次のように step を指定すると step 刻みで部分列をコピーすることができます。

Hy                      Python
-----------------------------------------
(cut S start end step)  S[start:end:step]
(cut S start None step) S[start::step]
(cut S None end step)   S[:end:step]
(cut S None None step)  S[::step]

簡単な実行例を示します。

=> (setv s [0 1 2 3 4 5 6 7])
=> (cut s)
[0 1 2 3 4 5 6 7]
=> (cut s 2 6)
[2 3 4 5]
=> (cut s 2 None)
[2 3 4 5 6 7]
=> (cut s None 6)
[0 1 2 3 4 5]
=> (cut s None None 2)
[0 2 4 6]
=> (cut s None 6 2)
[0 2 4]
=> (cut s 1 None 2)
[1 3 5 7]
=> (cut s 1 6 2)
[1 3 5]

●条件分岐

条件分岐には if を使います。

(if test then else)

Common Lisp とは違い、Hy の if は else 節を省略することができません。真偽の判定は Python と同じです。真偽は True / False だけではありません。数値 0 や 0.0 は偽を表し、それ以外の数値は真となります。文字列の場合、空文字列 "" は偽を表し、それ以外の文字列は真と判断されます。また、空のコレクション、[], #(), {}, #() や None というデータ型も偽を表します。

=> (if True 1 2)
1
=> (if False 1 2)
2
=> (if 10 1 2)
1
=> (if 0 1 2)
2
=> (if "foo" 1 2)
1
=> (if "" 1 2)
2
=> (if [1] 1 2)
1
=> (if [] 1 2)
2

then 節や else 節に複数の S 式を書きたいときは do を使います。

(do expr1 expr2 ... exprn)

do は Common Lisp の progn と同じです。引数の式を順番に評価し、最後に評価した式の結果を返します。

=> (do (print "foo") (print "bar") (print "baz") (+ 1 2))
foo
bar
baz
3

cond もありますが、Common Lisp とはちょっと違います。

(cond
  test-a expr-a
  test-b expr-b
   ...
  True   expr-z)

Common Lisp の場合、cond の後ろに節 (test-a expr-a1 expr-a2 ... expr-az) を並べますが、Hy では test と真のときに実行する式を書きます。複数の式を書きたいときは do を使ってください。

=> (setv n 5)
=> (cond (< n 0) -1 (= n 0) 0 True 1)
1
=> (setv n -5)
=> (cond (< n 0) -1 (= n 0) 0 True 1)
-1
=> (setv n 0)
=> (cond (< n 0) -1 (= n 0) 0 True 1)
0

数の比較には次の関数を使います。

<, <=, =, !=, >, >=

これらの関数は Common Lisp と同様に複数の引数を受け取ることができます。

=> (< 1 2 3 4 5)
True
=> (< 1 2 3 4 3)
False
=> (= 1 1 1 1 1)
True
=> (= 1 1 2 1 1)
False

●関数

Common Lisp は関数定義に defun を使いますが、Hy は Clojure と同じく defn を使います。

(defn 関数名 [仮引数 ...] expr-a ... expr-z)

仮引数は [ ] で囲みます。defn で定義された関数は、本体の式を順番に評価し、最後に評価した式の結果を返します。

=> (defn square [n] (* n n))
=> (square 10)
100
=> (defn medium [a b] (/ (+ a b) 2))
=> (medium 1 2)
1.5

Python と同様にデフォルト引数やキーワード引数を使用することができます。

(defn func [a b ... [x default1] [y default2]] ...)
=> (defn foo [a [b 10] [c 100]] (print a b c))
=> (foo 1)
1 10 100
=> (foo 1 2)
1 2 100
=> (foo 1 2 3)
1 2 3
=> (foo 1 :c 2 :b 3)
1 3 2
=> (foo :c 30 :a 10 :b 20)
10 20 30

引数名にはないキーワード引数を受け取りたい場合は、受け取る引数の前に #** を指定します。引数に定義されていないキーワード引数はディクショナリに格納されて、#** で指定した変数に渡されます。

=> (defn bar [a b c #** d] (print a b c d))
=> (bar 1 2 3 :foo 10 :bar 20)
1 2 3 {'foo': 10, 'bar': 20}

ディクショナリを展開して関数に渡したい場合も #** を使います。

=> (foo #** {"a" 10 "b" 20 "c" 30})
10 20 30

キーワード引数を使わずに、引数よりも多くの値を受け取りたい場合は、受け取る引数の前に #* を指定します。仮引数に入りきらない値は、タプルに格納されて #* を付けた変数に渡されます。リストやタプルを展開して関数に渡したい場合も #* を使います。

=> (defn baz [#* x] (print x))
=> (baz)
()
=> (baz 1)
(1,)
=> (baz 1 2 3)
(1, 2, 3)
=> (foo #* #(100 200 300))
100 200 300
=> (foo #* [100 200 300])
100 200 300

もちろん、再帰定義もできます。

=> (defn fact [n] (if (= n 0) 1 (* n (fact (- n 1)))))
=> (fact 9)
362880
=> (fact 10)
3628800
=> (fact 11)
39916800

=> (defn fibo [n] (if (< n 2) n (+ (fibo (- n 1)) (fibo (- n 2)))))
=> (fibo 10)
55
=> (fibo 11)
89
=> (fibo 12)
144

局所変数は let で定義しますが、Common Lisp とはちょっと違います。

(let [var1 init1 var2 init2 ...] form ...)

Common Lisp は変数と初期値をカッコ ( ) で囲みますが、Hy の場合は並べて書きます。

=> (setv a 10)
=> a
10
=> (let [a 100] (print a) a)
100
100
=> a
10
=> (setv b 20)
=> (let [a 100 b a] (print a b) [a b])
100 100
[100 100]
=> a
10
=> b
20

Hy の let は Common Lisp の let* と同じ動作になります。

●繰り返し

単純な繰り返しは for や while を使うと便利です。Python の for や while と同様の動作をします。

(for [var iterable] expr ...)
(while test expr ...)

for の変数 var はタプルやリストを使って多重代入することができます。

=> (for [x (range 5)] (print x))
0
1
2
3
4
=> (for [x [1 2 3 4 5]] (print x))
1
2
3
4
5
=> (for [k {"foo" 10 "bar" 20 "baz" 30}] (print k))
foo
bar
baz
=> (for [[k v] (.items {"foo" 10 "bar" 20 "baz" 30})] (print k v))
foo 10
bar 20
baz 30
=> (do (setv x 5) (while (< 0 x) (print "hello, Hylang!") (setv x (- x 1))))
hello, Hylang!
hello, Hylang!
hello, Hylang!
hello, Hylang!
hello, Hylang!

range は Python の range と同じです。

(range end)
(range start end)
(range start end step)

Python と同様に break と continue で繰り返しを制御することができます。

(break)
(continue)

簡単な例として、FizzBuzz 問題を解いてみましょう。

=> (defn change-fizzbuzz [n]
(cond (= (% n 15) 0) "FizzBuzz" (= (% n 3) 0) "Fizz" (= (% n 5) 0) "Buzz" True n))
=> (for [n (range 1 101)] (print (change-fizzbuzz n) :end " "))
1 2 Fizz 4 Buzz Fizz 7 8 Fizz Buzz 11 Fizz 13 14 FizzBuzz 16 17 Fizz 19 Buzz Fizz 22 23 Fizz Buzz 
26 Fizz 28 29 FizzBuzz 31 32 Fizz 34 Buzz Fizz 37 38 Fizz Buzz 41 Fizz 43 44 FizzBuzz 46 47 Fizz 49 Buzz 
Fizz 52 53 Fizz Buzz 56 Fizz 58 59 FizzBuzz 61 62 Fizz 64 Buzz Fizz 67 68 Fizz Buzz 71 Fizz 73 74 FizzBuzz
76 77 Fizz 79 Buzz Fizz 82 83 Fizz Buzz 86 Fizz 88 89 FizzBuzz 91 92 Fizz 94 Buzz Fizz 97 98 Fizz Buzz =>

●内包表記

Python の内包表現は Hy でも使うことができます。

内包表記は iterable から要素を順番に取り出して変数 var にセットし、最後の式 expr (key, value) を評価した結果をコレクションにセットします。var iterable は複数指定することができます。また、キーワードを使って内包表記の動作を制御することができます。よく使われるキーワードを以下に示します。

簡単な使用例を示します。

=> (lfor x (range 8) x)
[0 1 2 3 4 5 6 7]
=> (lfor x (range 8) (* x x))
[0 1 4 9 16 25 36 49]
=> (lfor [x y] (zip [1 2 3] [4 5 6]) (+ x y))
[5 7 9]
=> (lfor x [1 2 3] y [4 5 6] [x y])
[[1 4] [1 5] [1 6] [2 4] [2 5] [2 6] [3 4] [3 5] [3 6]]
=> (lfor x [1 2 3] y [4 5 6] :if (= (% (+ x y) 2) 0) [x y])
[[1 5] [2 4] [2 6] [3 5]]

=> (dfor x (range 8) (str x) (* x x))
{"0" 0  "1" 1  "2" 4  "3" 9  "4" 16  "5" 25  "6" 36  "7" 49}
=> (setv d {"foo" 1 "bar" 2 "baz" 3})
=> d
{"foo" 1  "bar" 2  "baz" 3}
=> (dfor [x y] (.items d) x (* y 10))
{"foo" 10  "bar" 20  "baz" 30}

=> (sfor x (range 8) x)
#{0 1 2 3 4 5 6 7}
=> (sfor x [1 2 3 1 2 3 4 1 2 3 4 5] x)
#{1 2 3 4 5}

=> (list (gfor x (range 8) x))
[0 1 2 3 4 5 6 7]
=> (setv g (gfor x (range 8) (* x x)))
=> g
<generator object <genexpr> at 0x7fbd8c21bdf0>
=> (next g)
0
=> (next g)
1
=> (next g)
4
=> (next g)
9
=> (next g)
16
=> (next g)
25
=> (next g)
36
=> (next g)
49

●高階関数と無名関数

Hy の場合、ラムダ式 (lambda) は fn で表します。

(fn [args ...] form ...)

他の関数型言語では「無名関数」とか「匿名関数」と呼ぶことがあります。

=> (fn [x] (* x x))
<function <lambda> at 0x7fbd8c283be0>
=> ((fn [x] (* x x)) 10)
100
=> (map (fn [x] (* x x)) [1 2 3 4 5])
<map object at 0x7fbd8c23f5b0>
=> (list (map (fn [x] (* x x)) [1 2 3 4 5]))
[1 4 9 16 25]
=> (list (filter (fn [x] (= (% x 2) 0)) [1 2 3 4 5]))
[2 4]

Python の関数 map と filter は Hy でも利用することができます。なお、Hy (Python) には内包表記があるので、map や filter よりもそちらを使ったほうが便利かもしれません。

=> (defn change-fizzbuzz [n]
... (cond (= (% n 15) 0) "FizzBuzz" (= (% n 3) 0) "Fizz" (= (% n 5) 0) "Buzz" True (str n)))
=> (list (map change-fizzbuzz (range 1 101)))
["1" "2" "Fizz" "4" "Buzz" "Fizz" "7" "8" "Fizz" "Buzz" "11" "Fizz" "13" "14" "FizzBuzz" "16" "17"

・・・ 略 ・・・

"91" "92" "Fizz" "94" "Buzz" "Fizz" "97" "98" "Fizz" "Buzz"]
=> (lfor n (range 1 101) (change-fizzbuzz n))
["1" "2" "Fizz" "4" "Buzz" "Fizz" "7" "8" "Fizz" "Buzz" "11" "Fizz" "13" "14" "FizzBuzz" "16" "17"

・・・ 略 ・・・

"91" "92" "Fizz" "94" "Buzz" "Fizz" "97" "98" "Fizz" "Buzz"]

もちろん、無名関数は「クロージャ」として動作します。

=> (defn adder [n] (fn [x] (+ x n)))
=> (setv add10 (adder 10))
=> (add10 123)
133
=> (add10 1234)
1244

ただし、クロージャ内の局所変数の値を書き換えるときは注意が必要です。Hy の場合、次のプログラムはエラーになります。

=> (defn make-int-seq [] (let [n 0] (fn [] (setv n (+ n 1)) n)))
=> (setv g (make-int-seq))
=> (g)
Traceback (most recent call last):
  File "stdin-b80de6596199c72ec41cbc932cd83a99055a19b7", line 1, in 
    (g)
  File "stdin-444958d42483c01d3c5253e19400308217b3558e", line 1, in _hy_anon_var_3
    (defn make-int-seq [] (let [n 0] (fn [] (setv n (+ n 1)) 1)))
UnboundLocalError: local variable 'n' referenced before assignment

Python でも同じエラーが発生します。

>>> def make_int_seq():
...     n = 0
...     def _int_seq():
...         n += 1
...         return n
...     return _int_seq
...
>>> g = make_int_seq()
>>> g()
Traceback (most recent call last):
  File "", line 1, in 
  File "", line 4, in _int_seq
UnboundLocalError: local variable 'n' referenced before assignment

Python の場合、変数への代入が行われると、その変数はローカル変数として扱われます。入れ子の関数で代入を行うと、その変数は入れ子の関数のローカル変数として扱われるため、外側の関数のローカル変数を隠してしまいます。値を書き換えたい場合は入れ子の関数で nonlocal 宣言する必要があります。この制約は Hy ver 0.28 でも同じようです。

=> (defn make-int-seq [] (let [n 0] (fn [] (nonlocal n) (setv n (+ n 1)) n)))
=> (setv g (make-int-seq))
=> (g)
1
=> (g)
2
=> (g)
3
=> (g)
4
=> (g)
5

●モジュールのインポート

モジュール (ライブラリ) の読み込みは import で行います。

=> (import math)
=> (math.sqrt 2)
1.4142135623730951
=> (import fractions [Fraction])
=> (setv x (Fraction 1 2))
=> x
(Fraction 1 2)
=> (setv y (Fraction 1 3))
=> y
(Fraction 1 3)
=> (+ x y)
(Fraction 5 6)
=> (- x y)
(Fraction 1 6)
=> (* x y)
(Fraction 1 6)
=> (/ x y)
(Fraction 3 2)

(import module) は Python の import module と同じ、(import module [name]) は from module import name と同じです。Python の from module import * は (import module *) となります。as で別名を付ける場合は :as を使います。たとえば、Python で import sympy as sy とするときは、(import sympy :as sy) とします。fractions は分数 (有理数) を扱うモジュールで、Fraction は分数を表すクラスです。fractions を import することで、Hy でも分数の計算を行うことができます。

Python のモジュールと同様に、Hy のプログラムも import で読み込むことができます。

;;;
;;; sample.py : Hylang sample program
;;;
;;;             Copyright (C) 2024 Makoto Hiroi
;;;

;;; 単純な関数
(defn 1+ [n] (+ n 1))
(defn 1- [n] (- n 1))
(defn square [n] (* n n))
(defn cubic [n] (* n n n))
(defn half [a] (/ a 2))
(defn medium [a b] (half (+ a b)))
(defn square-medium [a b] (medium (square a) (square b)))
(defn zerop [n] (= n 0))
(defn plusp [n] (< 0 n))
(defn minusp [n] (< n 0))
(defn sign [n] (if (zerop n) 0 (if (plusp n) 1 -1)))
(defn evenp [n] (zerop (% n 2)))
(defn oddp [n] (not (evenp n)))
(defn between [n low high] (<= low n high))
=> (import sample *)
=> (1+ 10)
11
=> (1- 10)
9
=> (oddp 1)
True
=> (oddp 2)
False

●オブジェクト指向

Hy の場合、クラスは defclass で、メソッドは defn で定義します。

(defclass class-name [super-class ...]
  (def __init__ [self args ...] ...)
  (def method-name [self args ...] ...)
  ...
)
=> (defclass Foo []
... (defn __init__ [self x] (setv self.x x))
... (defn get-x [self] self.x)
... (defn put-x [self n] (setv self.x n)))
=> (setv a (Foo 123))
=> (.get-x a)
123
=> (.put-x a 1234)
=> (.get-x a)
1234
=> (a.get-x)
1234
=> (a.put-x 12345)
=> (a.get-x)
12345

メソッドは (.method object ...) という形式で呼び出しますが、(object.method ...) としても呼び出すことができます。

リスト : Point クラス

(import math)

(defclass Point []
  (defn __init__ [self x y] (setv self.x x self.y y))
  (defn distance [self other]
    (let [dx (- self.x other.x) dy (- self.y other.y)]
         (math.sqrt (+ (** dx 2) (** dy 2))))))
=> (import sample *)
=> (setv p1 (Point 0 0))
=> (setv p2 (Point 1 1))
=> (.distance p1 p2)
1.4142135623730951

●マクロ

Hy のマクロ (macro) は伝統的なマクロといって、基本は Common Lisp のマクロと同じです。詳しい説明は拙作のページ Common Lisp 入門: マクロ をお読みくださいませ。ここでは Hy のマクロについて簡単に説明します

Hy のマクロは Common Lisp と同じく defmacro で定義します。

(defmacro macro-name [args ...] expr ...)

defmacro の構文は defn と同じです。defmacro で定義されたマクロは、次のような特徴を持ちます。

  1. 引数は評価されない。
  2. expr (S 式) を順番に評価し、いちばん最後の評価結果を再度評価して、その結果を返す。

この 2 番目の機能が Lisp におけるマクロの特徴です。これを図に示すと、次のようになります。

 [S式] --  評価 --> [新しいS式] -- 評価 --> [マクロの返り値] 

        (マクロ展開)

                    図 : マクロの動作

S 式を評価することで新しい S 式を組み立てます。この部分がマクロ展開に相当します。そして、その S 式を評価した値がマクロの返り値となります。

マクロを定義する場合、「バッククォート」という機能を使うと便利です。

(quote expr)
(quasiquote expr)
(unquote expr)
(unquote-splicing expr)

quote (') は S 式をデータとして扱うための基本的な機能です。Hy は式 'expr を読み込むと (quote expr) に変換します。quote は引数 expr を評価せずにそのまま返します。expr がリストの場合、expr を関数として評価せずにデータとして扱うことができます。

=> (+ 1 2 3)
6
=> '(+ 1 2 3)
'(+ 1 2 3)

quasiquote は quote と同様に引数の評価を行いません。Hy は式 `expr を読み込むと (quasiquote expr) に変換します。この中で ~ で始まる S 式があると、その S 式を評価した値で置き換えられます。Lisp / Scheme では ~ ではなく , が用いられます。Clojure も ~ を使うようです。簡単な例を示しましょう。

=> (setv v 'pen)
=> v
'pen
=> `(this is a ~v)
'(this is a pen)

変数 v にはシンボル pen がセットされています。次の S 式の中で ~v は v を評価した値、つまり pen に置き換わるのです。また、S 式の評価結果がリストの場合は、~@ を使うことができます。~@ を使うと、リスト (Hy の場合は [...] や #(...) でも OK) をはずした値と置き換えられます。次の例を見てください。

=> (setv w '(pen))
=> w
'(pen)
=> `(this is a ~w)
'(this is a (pen))
=> `(this is a ~@w)
'(this is a pen)

今度は変数 w にリスト (pen) がセットされました。次の S 式の中で -w は (pen) に置き換わります。そして、その次の S 式の中では、~@w は pen に置き換わるのです。それから、~ や ~@ は ` の中でしか使うことができません。ほかの S 式の中で評価した場合はエラーとなります。ご注意くださいませ。

簡単な例題として、Common Lisp のマクロ prog1 を作ってみましょう。

(prog1 fst snd ...) => fst の評価結果を返す

Common Lisp の prog は Hy の do と同じで、S 式を順番に評価して、最後に評価した S 式の結果を返します。prog1 は S 式を順番に評価するところは同じですが、最初に評価した S 式の結果を返すところが異なります。プログラムは次のようになります。

リスト : test_macro.hy

(defmacro prog1 [fst #* rest]
  `(let [result ~fst] (do ~@rest) result))

先頭の S 式は引数 fst に、残りの S 式は引数 rest に格納されます。fst を評価した結果を局所変数 result にセットし、do で rest の S 式を順番に評価します。最後に result を返します。

それでは実行してみましょう。Hy の場合、マクロは require で読み込みます。

(require source_file [...])

import ではマクロを読み込むことができません。ご注意くださいませ。

=> (require test_macro *)
=> (hy.macroexpand-1 '(prog1 1 2 3))
'(let [result 1] (do 2 3) result)
=> (hy.macroexpand-1 '(prog1 (+ 1 2) (print 3) (print 4)))
'(let [result (+ 1 2)] (do (print 3) (print 4)) result)

=> (prog1 1 2 3)
1
=> (prog1 (+ 1 2) (print 3) (print 4))
3
4
3

hy.macroexpand-1 はマクロ展開の結果を出力する関数です。正常に動作していますね。


●プログラムリスト1

;;;
;;; sample1.py : Hylang sample program
;;;
;;;              Copyright (C) 2024 Makoto Hiroi
;;;
(import math)

;;; 単純な関数
(defn 1+ [n] (+ n 1))
(defn 1- [n] (- n 1))
(defn square [n] (* n n))
(defn cubic [n] (* n n n))
(defn half [a] (/ a 2))
(defn medium [a b] (half (+ a b)))
(defn square-medium [a b] (medium (square a) (square b)))
(defn zerop [n] (= n 0))
(defn plusp [n] (< 0 n))
(defn minusp [n] (< n 0))
(defn sign [n] (if (zerop n) 0 (if (plusp n) 1 -1)))
(defn evenp [n] (zerop (% n 2)))
(defn oddp [n] (not (evenp n)))
(defn between [n low high] (<= low n high))

;;; FizzBuzz 問題
(defn change-fizzbuzz [n]
  (cond
   (= (% n 15) 0) "FizzBuzz"
   (= (% n 3) 0) "Fizz"
   (= (% n 5) 0) "Buzz"
   True (str n)))

(defn fizzbuzz []
  (for [n (range 1 101)] (print (change-fizzbuzz n))))

(defn fizzbuzz1 []
  (lfor n (range 1 101) (change-fizzbuzz n)))

;;; 階乗
(defn fact [n]
  (if (= n 0)
      1
    (* n (fact (1- n)))))

;;; オプショナル引数は [var init] で指定する
(defn fact1 [n [a 1]]
  (if (zerop n)
      a
    (fact1 (1- n) (* a n))))

(defn fact2 [n]
  (let [a 1] (for [i (range 1 (1+ n))] (setv a (* a i))) a))

;;; フィボナッチ数
(defn fibo [n]
  (if (< n 2)
      n
    (+ (fibo (1- n)) (fibo (- n 2)))))

(defn fibo1 [n [a 0] [b 1]]
  (if (zerop n)
      a
    (fibo1 (1- n) b (+ a b))))

(defn fibo2 [n]
  (let [a 0 b 1 c 0] (for [i (range n)] (setv c a a b b (+ b c))) a))

;;; 累乗
(defn power [x n]
  (if (zerop n)
      1
    (* x (power x (1- n)))))

(defn power1 [x n [a 1]]
  (if (zerop n)
      a
    (* (power1 x (1- n) (* a x)))))

(defn power2 [x n]
  (cond
   (zerop n) 1
   (= n 1) x
   True (let [z (power2 x (// n 2))]
             (if (oddp n) (* z z x) (* z z)))))

;;; 最大公約数
(defn gcd [a b]
  (if (zerop b) a (gcd b (% a b))))

(defn gcd1 [a b]
  (while (plusp b)
    (setv [a b] [b (% a b)]))
  a)

;;; Point
(defclass Point []
  (defn __init__ [self x y] (setv self.x x self.y y))
  (defn distance [self other]
    (let [dx (- self.x other.x) dy (- self.y other.y)]
         (math.sqrt (+ (** dx 2) (** dy 2))))))

Copyright (C) 2024 Makoto Hiroi
All rights reserved.

[ Home | Light | Python3 ]