LisperのためのHy (hylang)

他のLisp系言語をそれなりに知っている人がHyについて簡単に知るためのメモ。随時更新。

# 変更履歴
- 2024-11-10 Hy 1.0.0対応。旧版の仕様の記述は削除
- 2025-05-04 クラス定義、マクロなど加筆

# Overview
- シンタックスはClojureに近い。ただしプログラミングスタイルはPythonに近い
  - ClojureやSchemeと同様に変数と関数で名前空間が分かれていない（Lisp-1）
  - Common LispやClojureに近いスタイルのマクロが使える（いわゆる伝統的マクロ）
- Pythonを呼び出したり、逆にPythonから呼び出すこともでき、Hyのコードから対応するPythonのコードを生成することもできる
- 1.0に向けて破壊的変更が激しくなっている。より生のPythonに近いシンタックスに寄せてきている印象
  - 一時期1.0のalpha版ということで`1.0a4`といったバージョンになっていたがそれ以降は`0.24.0`に戻された。
  - [2024/09/22に1.0.0がリリースされた](https://github.com/hylang/hy/releases/tag/1.0.0)。今後は仕様が安定することが期待される

# 変数
変数の導入、設定には`setv`を使う。
`setv`は値を返さないので、設定した値を返す`setx`もある。これはPythonのセイウチ演算子(`:=`)に展開される。

```hy
(setv x 10)
;; => None

(setx x 10)
;; => 10
```

## `let`
局所変数を導入するためには`let`が使える。
スタイルとしてはClojureの`let`に近く、局所変数と値のペアが括弧で括られずフラットに並ぶ。
Clojureの`let`やCommon Lispの`let*`と同じで、1つの`let`の中で前に設定した変数をその後の変数の初期化に利用できる。

Pythonに変換したとき、ユニークな名前の変数に代入しているだけのようである。
そのためレキシカルスコープは作らないことに注意が必要。
```hy
(let [x 10
      y (+ x 20)]
  (print x y))

;; 変換後のPythonコード
; _hy_let_x_2 = 10
; _hy_let_y_3 = _hy_let_x_2 + 20
; print(_hy_let_x_2, _hy_let_y_3)
```

# 関数

## 関数適用
他のLispと同様、関数名をリストの最初に置くと関数適用となる。
リストの2要素目以降（引数）が左から右へ順に評価されていき、全ての値が出揃った時点でリスト先頭の関数が適用される(正格評価)。

```hy
(+ 1 2 3) ; => 6
```

Common Lispとは違い、関数と変数で名前空間が分かれておらず、1つの名前空間を共有している。Scheme、Clojure、Pythonと同じだ。
そのため、値として渡ってきた関数を適用するときも`funcall`などは不要で、単純にリストの先頭に置くだけである。
以下は関数を返す関数を定義し、返された関数を5に適用している例。

```hy
(defn add-n-generator [n]
  (fn [x] (+ x n)))

((add-n-generator 10) 5) ; => 15
```

### `#*` , `#**` リーダーマクロ
多くのLispには、引数として関数とリストを受け取り、リストを展開して与えられた関数に適用させるために`apply`が用意されている。
Hyでは`apply`の代わりに`#*`リーダーマクロを使う。リストの前に`#*`をつけることがでそのリストが展開される。

```hy
(+ #*'(1 2 3)) ; => 6
(+ #*[1 2 3])  ; => 6

;; #*を並べて書くこともできる
(+ #*[1 2 3] #*[4 5]) ; => 15
```

リーダーマクロ `#*` をシンボルやタプルの前に付けるときはスペースが必要なことに注意。

```hy
(setv lst [1 2 3])

(+ #* lst) ; => 6

;; タプルの前にもスペースが必要
(+ #* #(1 2 3)) ; => 6
```

同様に、辞書型のデータを展開して関数にキーワード引数として与えることもできる。それには `#**` を使う。

```hy
(defn keyword-arg-func [#** kwargs]
  (print "kwargs: " kwargs))

(keyword-arg-func :key1 1 :key2 2)
; kwargs:  {'key1': 1, 'key2': 2}

(keyword-arg-func #**{"key1" 1, "key2" 2})
; kwargs:  {'key1': 1, 'key2': 2}

;; リストと同様、辞書型を変数に格納してキーワード引数に渡すときは #** の後にスペースが必要
(setv dict1 {"key1" 1, "key2" 2})
(keyword-arg-func #** dict1)
```

これを利用して、2つの辞書データをマージすることもできる。

```hy
{#**{"key1" "value1"} #**{"key2" "value2"}}
; {"key1" "value1"  "key2" "value2"}
```

## 関数定義
関数定義はほぼClojureと同じだが、docstringの位置が異なり、引数リストの後に持ってくる必要がある。なおdocstringはオプショナルである。
例えば、階乗の定義は以下のようになる。他のLisp系言語と同様に、明示的にreturnは必要なく、最後に評価される式が返される。

```hy
(defn fact [n]
  "docstring for fact"
  (if (= n 0)
      1
      (* n (fact (- n 1)))))
```

### 引数オプション

#### 可変長引数

例えば`+`のように、可変長引数を取る関数を定義するときには、関数定義の仮引数の前に `#*` を置く。
例えば以下の例では、`args`が可変長引数のリストになる。`#*`にシンボルが続く場合には間にスペースが必要なことに注意する。

```hy
(defn my-plus [#* args]
  (+ #* args))

(my-plus 1 2 3) ; => 6
```

#### オプショナル引数
オプショナルな引数を持つ関数を定義するには、引数リストの中で`[optional-parameter default-value]`のようにパラメータ名とデフォルト値のペアを指定する。
これはオプショナル引数とキーワード引数の複合のような指定の仕方になっており、この`optional-parameter`の部分が仮引数、兼キーワード指定子になっている。
オプショナル引数は明示的にキーワードを指定して呼び出すことも、指定しないで呼び出すこともできる。
```hy
(defn opt-arg-func [arg1 [key1 12] [key2 34]]
  [arg1 key1 key2])

(opt-arg-func "foo")                   ; ["foo" 12 34]
(opt-arg-func "foo" :key1 21)          ; ["foo" 21 34]
(opt-arg-func "foo" :key2 43)          ; ["foo" 12 43]
(opt-arg-func "foo" :key1 21 :key2 43) ; ["foo" 21 43]
(opt-arg-func "foo" 21 43)             ; ["foo" 21 43]
```

デフォルト値は必須。オプショナル引数の位置でデフォルト値を指定しないと構文エラーになる。

```hy
(defn opt-arg-func2 [[key1 12] [key2 34] key3]
  [key1 key2 key3])
;; =>hy.errors.HySyntaxError: non-default argument follows default argument
```

#### 任意形式のキーワード引数
仮引数の前に #** を付けることで、形式を定めずにキーワード引数として受け付け、関数内からは辞書型として参照できる。

```hy
(defn kwargs-test [#** kwargs]
  kwargs)

(kwargs-test :key1 "val1" :key2 "val2")
;; => {"key1" "val1"  "key2" "val2"}
```

ここまで出てきた引数オプションは組み合わせて使うこともできる。
以下は必須引数、オプショナル引数を取りつつ、さらに多くの可変長引数と追加のキーワード引数を受け取る関数の例である。

```hy
(defn f [required1 required2
         [optional1 None] [optional2 "foo"]
         #* rest #** kwargs]
  (print (.format "required1: {0}, required2: {1}, optional1: {2}, optional2: {3}, rest: {4}, kwargs: {5}"
                  required1 required2 optional1 optional2 rest kwargs)))

(f 1 2 3 4 5 6 7 8 :nine 9 :ten 10)
;; required1: 1, required2: 2, optional1: 3, optional2: 4, rest: (5, 6, 7, 8), kwargs: {'nine': 9, 'ten': 10}

(setv lst [5 6 7 8])
(f 1 2 3 4 #* lst)
;; required1: 1, required2: 2, optional1: 3, optional2: 4, rest: (5, 6, 7, 8), kwargs: {}

(setv dic {"nine" 9, "ten" 10})
(f 1 2 3 4 #* lst #** dic)
;; required1: 1, required2: 2, optional1: 3, optional2: 4, rest: (5, 6, 7, 8), kwargs: {'nine': 9, 'ten': 10}
```

## 無名関数
無名関数を作るには`lambda`ではなく、ClojureやArcと同様に`fn`を使う。
本体部分が1つの式を返すだけであればPythonの`lambda`に変換されるが、複数の式から構成されている場合は`_hy_anon_var_XX`などの名前付き関数に変換される。これらは変換後の関数名を知っていれば呼び出せてしまうので注意が必要である。

```hy
;; 本体部分が1つの式のとき
(fn [x] (+ x 1)) ; => <function <lambda> at 0x7fbd9062e8b0>

;; 式の最初の要素に置いて引数を与えれば適用できる
((fn [x] (+ x 1)) 10) ; => 11

;; 本体部分に複数の式が含まれている場合
(fn [x] (print x) (+ x 1))

; # Python変換後
; def _hy_anon_var_5(x):
;     print(x)
;     return x + 1
; _hy_anon_var_5
```

Hyでは関数と変数の名前空間が分かれていないので、次のように無名関数を変数に代入することでも関数を定義できる。

```hy
(setv add
      (fn [x y]
        (+ x y)))

(add 2 3) ; => 5
```

## ドキュメント検索

`help`で関数のdocstringを参照できる。

```hy
(help fact)

; fact(n)
;     docstring for fact
```

Common Lispの`apropos`では、現在の処理系にロードされているパッケージを横断してドキュメントを検索できた。これに相当するようなものとしては `pydoc` がある。
これはCommon Lispのaproposとは違って、現在ロードされているものではなく、インストールされているパッケージ全体を検索する。そのため若干時間がかかることに注意が必要。

```hy
(import pydoc)
(pydoc.apropos "multiply")

; scipy.sparse.linalg._expm_multiply - Compute the action of the matrix exponential.
; scipy.sparse.linalg.tests.test_expm_multiply - Test functions for the sparse.linalg._expm_multiply module.
```


## 高階関数
HyではLispやPythonと同様に、関数がファーストクラスオブジェクトである。そのため、関数を関数に渡したり、値として返すことができる。
`map`, `filter` はPython組込みのものが使える、`reduce`はPythonの標準ライブラリの`functools`の中にある。
これらが返してくるシーケンスはmapクラスやfilterクラスのオブジェクトなので、`list`関数などで利用する型に明示的に変換する必要がある。

### `map`の例
```hy
(defn add1 [n]
  (print f"add1(n = {n})")
  (add n 1))

(setv seq [1 2 3])

(list (map add1 seq)) ; => [2 3 4]

;; 出力:
;; add1(n = 1)
;; add1(n = 2)
;; add1(n = 3)
```

遅延評価を使いたい場合は、サードパーティライブラリの`toolz`などを使用する。

```hy
(import toolz [take])

(list (take 2 (map add1 seq))) ; => [2 3]

;; takeは必要な要素しか評価しない
;; add1(n = 1)
;; add1(n = 2)
```

### `filter`の例

```hy
(defn even? [n]
  (= (% n 2) 0))

(list (filter even? seq)) ; => [2]
```

### `reduce`の例

```hy
(import functools [reduce])

(defn add [a b]
  (+ a b))

(reduce add seq 0) ; => 6
```

### その他のreducer

mapやfilterオブジェクトに対して、全ての要素が真であるかどうか判定するときには`all`を使う。Common Lispの`every`に近いがこれ自体が高階関数なわけでない。

```hy
(setv lst [1 2 3 4])

(all (map (fn [n] (is (type n) int)) lst)) ; => True
(all (map even? lst)) ; => False
```

同様に、1つでも真の要素があるかどうかの判定には`any`を使う。こちらのはCommon Lispの`some`に近い。

```hy
(any (map even? lst)) ; => True
(any (map (fn [n] (is (type n) float)) lst)) ; => False
```

他にitertoolsなども使える。例えば、reduceの溜め込みの途中経過をリストとして取得したければ`accumulate`が使える。

```hy
(import itertools [accumulate])

(list (accumulate (range 1 11) add)) ; => [1 3 6 10 15 21 28 36 45 55]
```

## 関数定義のバリエーション

### デコレータ
デコレータとは、関数を引数に取って、何らかの処理を噛ませた後に新たな関数として返す関数として定義される。
Pythonで関数の前に `@decorator` のように書かれているものに相当する。
関数定義の際に、デコレータを適用させるには`defn`の直後に `[my-decorator]`のように書く。
例えば関数の前後に何らかの処理を挟みたいときは以下のようにする。

```hy
(defn my-decorator [func]
  (fn [#* args #** kwargs]
    (print "Before the function")
    (setv result (func #* args #** kwargs))
    (print "After the function")
    result))

(defn [my-decorator] my-function []
  (print "Inside the function"))

(my-function)
;; Before the function
;; Inside the function
;; After the function
```

デコレータは引数を取ることもできる。例えば、任意のメッセージを引数に取り、関数実行前にログを出力する機能を付加するデコレータは以下のようになる。
このデコレータを適用するには、`defn`の直後に `[(log-message "This is a test message")]`のように括弧付きで引数を指定する。

```hy
(defn log-message [message]
  (fn [func]
    (fn [#* args #** kwargs]
      (print f"=== Log: {message} ===")
      (func #* args #** kwargs))))

(defn [(log-message "This is a test message")]
  greet [name]
  (print f"Hello, {name}"))

(greet "Alice")
;; === Log: This is a test message ===
;; Hello, Alice
```

### 非同期関数定義
`defn`のバリエーションとしては、asyncioを使う際の非同期関数定義のための記法も用意されている。
これによって処理の途中で中断/再開するような関数を定義できる。
例えば、以下は非同期版のフィボナッチ数列の定義になる。
呼び出し側では `for` の拡張構文を必要とすることに注意。
```hy
(import asyncio)

(defn :async async-fib []
  (yield 1)
  (yield 1)
  (let [pre1 1
        pre2 1]
    (while True
      (setv tmp pre1)
      (setv pre1 (+ pre1 pre2))
      (setv pre2 tmp)
      (yield pre1))))

(defn :async get-first-n-fib [n]
  (setv result [])
  (setv count 0)
  (for [:async value (async-fib)] ; Pythonのasync forに相当
    (if (< count n)
        (do (.append result value)
            (setv count (+ count 1)))
        (break)))
  result)

(print (asyncio.run (get-first-n-fib 10)))
;; => [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
```

# 制御構造

## ブロック

### do
doは処理をブロックにまとめる構文で、内部の式を順次評価していき、最後の式の値をdoブロック全体の値として返す。レキシカルスコープは作らない。
Clojureの`do`, Common Lispの`progn`, Schemeの`begin`に相当する。

```hy
(do (print "foo")
    (print "bar")
    10)
; foo
; bar
; => 10
```

`let`も暗黙的に`do`のようなブロックを作る。これらは後述する`if`や`cond`の中でも使う。

## 条件分岐

### if
ifは他のLispとほぼ同じだが、真理値の扱いはPythonと同じ癖があり、色々なものが偽となりうるので注意が必要。

次のものが全て偽として扱われる。

- `False`
- `None`
- 零: `0`, `0.0`
- 空リスト:`[]`
- 空tuple:`#()`
- 空dict:`{}`
- 空のHyリスト:`'()`
- 空文字: `""`

```hy
(if (or False None 0 0.0 [] #() {} '() "")
    "This is true"
    "This is false")

; => "This is false"
```

他のLispと異なり、`if`は偽の式が必須であることに注意。3引数でないとは構文エラーになる。
条件が真の場合のみ評価したい場合は`when`を使う。`when`は条件に合わないときは`None`を返す。
```hy
(if True "This is true")
; hy.errors.HySyntaxError: parse error for pattern macro 'if': got unexpected end of macro call, expected: form

(when True "This is true")
```

### cond

`cond`より`if`より柔軟な条件分岐構文である。
Hyの`cond`はClojureの`cond`と同じく、条件部と本体部のペアを並べたものを列挙する方式だ。
本体部で複数の式を並べたいときは明示的にdoを入れる必要があることに注意。
以下の例ではデフォルト節の条件部は`:else`としているが、真になるものであれば何でもいい。

```hy
(defn fib [n]
  (cond (= n 0) 0
        (= n 1) 1
        :else (+ (fib (- n 1))
                 (fib (- n 2)))))

(list (map fib (range 1 11))) ; => [1 1 2 3 5 8 13 21 34 55]
```

### when / unless

`when`は`if`と`do`の組合せで、条件部が真(偽)のときに本体部分を順に評価していき最後の式の値を返す。

```hy
(when True
  (print "one")
  (print "two")
  3)
; one
; two
; => 3
```

`unless`は`when`の条件部を反転させたもので、多くのLispに存在するマクロである。Hyの場合はHyruleという標準ライブラリパッケージに入っている。
マクロなのでimportではなくrequireする必要がある。

```hy
(require hyrule [unless])

(unless False
  (print "one")
  (print "two")
  3)
; one
; two
; => 3
```

## 繰り返し

### シーケンスに対する繰り返し: for

```hy
(for [x ["a" "b" "c"]]
  (print x))

; a
; b
; c

(for [x (range 0 10)]
  (print x))

;; 0
;; 1
;; 2
;; 3
;; 4
;; 5
;; 6
;; 7
;; 8
;; 9
```

2つのループ変数を並べると二重ループになる。これならばforを二重にする方が意図が明確になる分まだいい気がする。

```hy
(for [i [1 2 3]
      j [3 2 1]]
  (print (.format "i: {0}, j: {1}" i j)))

; i: 1, j: 3
; i: 1, j: 2
; i: 1, j: 1
; i: 2, j: 3
; i: 2, j: 2
; i: 2, j: 1
; i: 3, j: 3
; i: 3, j: 2
; i: 3, j: 1

(for [i [1 2 3]]
  (for [j [3 2 1]]
    (print (.format "i: {0}, j: {1}" i j))))
```

Common Lispのloopマクロのforのように複数のシーケンスを並行して舐めていくには、`zip` を使うなどして複数のシーケンスの各要素のペアからなる1つのシーケンスとしてforを回す。
forのループ変数にも分配束縛のような書き方ができる。

```hy
(for [[i j] (zip [1 2 3] [3 2 1])]
  (print (.format "i: {0}, j: {1}" i j)))

; i: 1, j: 3
; i: 2, j: 2
; i: 3, j: 1
```

### while

より一般的な繰り返し構文として `while` もある。
これはwhileの後に真理値を取り、真である限り繰り返す構文だ。

```hy
(let [i 0]
  (while (< i 10)
    (print i)
    (setv i (+ i 1))))

;; 0
;; 1
;; 2
;; 3
;; 4
;; 5
;; 6
;; 7
;; 8
;; 9
```

途中で明示的に `(break)` を呼ぶことによっても脱出できる。

```hy
(let [i 0]
  (while (< i 10)
    (print i)
    (when (= i 5)
      (break))
    (setv i (+ i 1))))

;; 0
;; 1
;; 2
;; 3
;; 4
;; 5
```

### `loop` / `recur`マクロ
Pythonは末尾再帰最適化を行なわないので、普通に再帰呼び出しを行うとコールスタックを消費する。末尾再帰呼び出しを単なるループとして処理するために、Clojureの`loop/recur`構文と同じものがHyにも用意されている。
再帰呼び出しの関数名が`recur`に固定されていると考えると、Schemeの`named-let`にも似ている。
これはHyの標準ユーティリティ集であるhyruleに含まれている。マクロなので`require`でロードする必要がある。

以下の例はフィボナッチ数の定義を末尾再帰版に直したものである。
```hy
(require hyrule [loop])
(import hyrule [recur])

(defn fast-fib [n]
  (loop [[cnt 0]
         [pre 1]
         [prepre 0]]
    (if (= cnt n)
        pre
        (recur (+ cnt 1)
               (+ pre prepre) pre))))

(fast-fib 100) ; => 354224848179261915075
```

### リスト内包表記: lfor

繰り返しの結果をリストで取得したいときはPythonのリスト内包表記に展開される`lfor`を使う。
なおmapでも同じことはできる。

```hy
(lfor i (range 1 11) (fast-fib i))
;; => [1 1 2 3 5 8 13 21 34 55]

(list (map fast-fib (range 1 11)))
;; => [1 1 2 3 5 8 13 21 34 55]
```

### 辞書内包表記: dfor
同様に、シーケンスから辞書を作る辞書内包表記`dfor`がある。

```hy
(dfor x (range 5) x (* 2 x))
;; => {0 0  1 2  2 4  3 6  4 8}

(setv keys ["a" "b" "c"])
(setv values [1 2 3])

(dfor [k v] (zip keys values) k v)
;; => {"a" 1  "b" 2  "c" 3}
```

### 集合内包表記: sfor
同様に、集合を作るには`sfor`がある。

```hy
(sfor i (range 10) (* i i))
;; => #{0 1 64 4 36 9 16 49 81 25}
```

## 例外処理

例外処理には`try`構文が使える。
Common Lispの`handler-case`のようなものだと思ってよい。
本体部分には複数の式を書くことができ、`except`に指定したエラーを捕捉したときの処理を書ける。

```hy
(try
  ;; 本体部分
  (print "Start try body")
  (/ 1 0)
  (except [e ZeroDivisionError]
    ;; 例外をeに束縛して処理する
    (print f"Caught an error: {e}")
    e))

;; Start try body
;; Caught an error: division by zero
;; => ZeroDivisionError('division by zero')
```

`except`節は複数個並べることもできる。`else`節は例外が発生しなかったときに実行される。`finally`節は成否に関わらず通過する。

```hy
(try
  (/ 2 0)
  (except [e ZeroDivisionError]
    (print "Division by zero"))
  (except [e TypeError]
    (print "Type Error"))
  (else
    (print "No errors"))
  (finally
    (print "Guaranteed to pass through here")))

;; Division by zero
;; Guaranteed to pass through here
```

# データ構造

## リスト
Pythonのリスト。
要素へのアクセスには`get`を使う。`(get list1 0)`は`list1[0]`のようにPythonの `[]` リテラルに展開されるので、dictやtupleについても同じように使えることが分かる。

```hy
(type [1 2 3])
;; => <class 'list'>

(setv list1 [1 2 3])
(get list1 0)
;; => 1
```

`get`に複数の引数を渡すと入れ子になっているリストなどから値を取り出せる。

```hy
(setv mat [[1 2 3]
           [4 5 6]
           [7 8 9]])

(get mat 2 2) ; => 9
(get (get mat 2) 2) ; => 9
```

リストの特定の要素に代入するには setv と getを組み合わせる。

```hy
(setv (get list1 0) 10)
;; Python: list1[0] = 10

list1
;; => [10 2 3]
```

### スライス
スライスは最大3引数の `cut` に対応する。
https://qiita.com/yossyyossy/items/0c8dc2ed53466d970fe4

```hy
(let [from 1
      to None
      by 2]
  (cut [1 2 3 4 5] from to by)) ; => [2, 4]

(cut [1 2 3 4 5] 2 None) ; => [3, 4, 5]
(cut [1 2 3 4 5] 0 2) ; => [1, 2]
(cut [1 2 3 4 5] 2 4) ; => [3, 4]
(cut [1 2 3 4 5] -4 -2) ; => [2, 3]
```

## タプル
イミュータブルなリスト。
`#(1 2 3)` のようなリテラルで表記する。`tuple`関数によって作れる。
イミュータブルなので要素に値を代入しようとするとエラーになる。

```hy
(type #(1 2 3)
;; => <class 'tuple'>

(tuple [1 2 3])
;; => #(1 2 3)

(setv tuple1 #(1 2 3))
(setv (get tuple1 0) 10)
;; TypeError: 'tuple' object does not support item assignment
```

## set (集合型)

集合型。順序を持たず、要素の重複を許さない。

```hy
(type #{1 2 3})
;; <class 'set'>

(dir #{1 2 3})
; [... "add" "clear" "copy" "difference" "difference_update" "discard" "intersection" "intersection_update" "isdisjoint" "issubset" "issuperset" "pop" "remove" "symmetric_difference" "symmetric_difference_update" "union" "update"]

(set [1 2 3])
;; #{1 2 3}

(setv sets #{"a" "b" "c" "d"})
(print sets)
;; {'c', 'd', 'b', 'a'}

(sets.add "e")
(print sets)
;; {'c', 'e', 'b', 'a', 'd'}

(sets.remove "a")
(print sets)
;; {'c', 'e', 'b', 'd'}

(sets.pop)
;; => "c"
(print sets)
;; {'e', 'b', 'd'}
```

要素が集合に属するかどうかは `in` 演算子を使う。

```hy
(in 1 #{1 2 3}) ; => True
```

### 集合演算

```hy
;; 包含関係 (<, <=, >, >=)
(<= #{1 2} #{1 2 3}) ; => True
(<= #{1 2 3} #{1 2}) ; => False

;; 同値
(= #{1 2} #{1 2}) ; => True

(setv sets2 #{1 2 3})

;; 積集合
(sets2.intersection #{3 4 5}) ; => #{3}

;; 和集合
(sets2.union #{3 4 5}) ; => #{1 2 3 4 5}

;; 差集合
(sets2.difference #{3 4 5}) ; => #{1 2}
(- sets2 #{3 4 5}) ; => #{1 2}
```

## dict (辞書型)

属性リスト的な使い方で、keyとvalueを交互に並べる。
keyとvalueのペアの後にスペースを2つ入れるスタイルが推奨される(Clojureではカンマを入れるところだが)。
参照にはリストと同じで `get` を使い、 `setv` と組み合わせて代入できる。これは `assoc` でも同じことができる。

```hy
(setv d {"dog" "bark"  "cat" "meow"})
(type {"dog" "bark"  "cat" "meow"}) ; <type 'dict'>

(d.get "dog") ; => "bark"
(get d "dog") ; => "bark"

(setv (get d "dog") "wan"
      (get d "cat") "nyan")
d ; => {"dog" "wan"  "cat" "nyan"}

(require hyrule [assoc]) ;; Hy 1.0a4 からHyruleに分離され、requireが必要になった
(assoc d "dog" "bark") ; => None
d ; => {"dog" "bark"  "cat" "nyan"}

;; 辞書型に対するループ

(for [[k v] (d.items)]
  (setv formtxt "key: {0}, value: {1}")
  (print (formtxt.format k v)))

;; key: dog, value: bark
;; key: cat, value: nyan
```

前述の通り、関数のキーワード引数に展開して与えることもできる。

```hy
(defn say [[dog "bark"] [cat "meow"]]
  (print "Dog say" dog)
  (print "Cat say" cat))

(say #** d)

;; Dog say bark
;; Cat say nyan
```

## クラス

```hy
;;; クラス定義

(defclass Foo []
  ;; コンストラクタ
  (defn __init__ [self x]
    (setv self.x x))
  ;; アクセサ
  (defn get-x [self]
    self.x)
  (defn set-x [self value]
    (setv self.x value)))
```

インスタンス作成とメソッドへのアクセスは以下になる。
メソッドへのアクセス方法はいくつかある。

```hy
;; インスタンス作成
(setv foo-obj (Foo 1))

;; 以下は全て同じである
(.get-x foo-obj) ; => 1
(foo-obj.get-x)  ; => 1
(Foo.get-x foo-obj)  ; => 1

(foo-obj.set-x 2)
(foo-obj.get-x) ; => 2
```

### クラス継承

Fooクラスを継承するBarクラスは以下のように定義する。

```hy
;; Fooを継承するBar

(defclass Bar [Foo]
  ;; コンストラクタ
  (defn __init__ [self x y]
    (.__init__ (super Bar self) x) ; 親クラスの__init__メソッドを呼び出す
    (setv self.y y))
  ;; アクセサ
  (defn get-y [self]
    self.y))

(setv bar-obj (Bar 1 2))
(bar-obj.get-x) ; => 1
(bar-obj.get-y) ; => 2
```

### withマクロ (コンテキストマネージャ)
クラス定義の際に `__enter__` メソッドと `__close__` メソッドを定義しておくと、`with`マクロによりインスタンスを生成する際に前処理と後処理をはさむことができる。
Common Lispの`unwind-protect`のように、withの本体部分でエラーが発生しても `__exit__`部分は実行されるため、クリーンナップ処理を書くのに使える。

```hy
(defclass Greeting []
  (defn __enter__ [self]
    (print "in enter")
    "hello")
  (defn __exit__ [self #* args]
    (print "in exit")))

(with [(Greeting)]
  (print "body"))

;; in enter
;; body
;; in exit
```

## 式 (`Expression`)

見た目上は他のLispのリストに近く、クォートで評価から保護しない限り評価規則に従ってプログラムとして評価される。
Hyのコードはこのデータ型から成り立っており、マクロの操作対象のデータとなる。
Pythonのリストとは別のデータ構造として定義されており、データとしてリストが欲しい時は主にPythonのリストの方を使う。

```hy
;; リテラル
(type '(1 2 3))
;; => <class 'hy.models.Expression'>

;; コンストラクタ
(hy.models.Expression [1 2 3])
;; => '(1 2 3)
```
タプルを継承しており、`+`で結合したり`get`で要素を取得することもできる。

```
(+ '(1 2 3) '(4 5 6))
;; => '(1 2 3 4 5 6)

(get '(1 2 3) 1)
;; => '2
```

# マクロ
マクロ定義はCommon Lispに近い。いわゆる伝統的マクロで、`defmacro`を使って定義する。

## 例1: `my-when`
例えば、`when`を自前で定義してみると、以下のようになる。

```hy
(defmacro my-when [predicate #* body]
  `(if ~predicate
       (do ~@body)
       None))
```

式の前に準クォート（バッククォート）を付けると、通常のクォートと同じように式を評価から保護できるが、式の一部分だけを評価して評価結果を埋め込むことができる。
アンクォートは`~`、リストを展開するためのスプライシングアンクォートは`~@`で、ここはClojureと同じだ。

マクロの展開には`hy.macroexpand-1`が使える。

```hy
(hy.macroexpand-1 '(my-when True
                     (print "This is True")))

; (if True
;     (do (print "This is True"))
;     None)
```

## 例2： `lexical-let`
`let`は`lambda`のシンタックスシュガーでしかないので、自分で定義することもできる。
Hy標準の`let`はレキシカル環境を作らないが、以下のように定義すると、レキシカル版の`let`を作ることができる。

```hy
(defmacro lexical-let [var-pairs #* body]
  (setv var-names (lfor x var-pairs (get x 0))
        var-vals  (lfor x var-pairs (get x 1)))
  `((fn [~@var-names] ~@body) ~@var-vals))

(hy.macroexpand-1 '(lexical-let [[one 1]
                                 [two 2]
                                 [three 3]]
                     (print one two three)))

; => '((fn [one two three] (print one two three)) 1 2 3)
```


```hy
(macroexpand-1
  '(let* [one 1
          two (+ one one)
          [three four] [(+ one two) (+ two two)]] ; 展開形を見ると分配束縛もできることが分かる
     (print one two three four)))

;; '((fn []
;;     (setv one 1
;;           two (+ one one)
;;           [three four] [(+ one two) (+ two two)])
;;     (print one two three four)))
```

## 例: `hy.gensym`, `hyrule.with-gensyms`を使うケース
マクロ内で変数を導入するときの注意点として、マクロ展開後のスコープにたまたま同じ変数の名前があった場合、その変数の名前を奪ってしまい、想定していない挙動になる可能性がある。これを変数捕捉と呼び、適切に避ける必要がある。
多くのLisp系言語と同様に、Hyにも`hy.gensym`というユニークなシンボルを生成する関数がある。
また、より簡単に書くための`hyrule.with-gensyms`も用意されている。

例として、body部分の処理の時間を計測するためのマクロ `timeit` を書いてみる。

```hy
(require hyrule [with-gensyms])

(defmacro timeit [#* body]
  (with-gensyms (start end result)
    `(do
       (setv ~start (time.time))
       (setv ~result (do ~@body))
       (setv ~end (time.time))
       (print (+ (.format "Evaluation took: {0}" (- ~end ~start)) " seconds"))
       ~result)))

(timeit (fib 30))
;; Evaluation took: 1.526123046875 seconds
;; => 832040
```

これを展開すると、変数名がユニークな名前のシンボルになっていることが分かる。

```hy
(do (setv _hy_gensym_start_20 (time.time))
    (setv _hy_gensym_result_22 (do (fib 30)))
    (setv _hy_gensym_end_21 (time.time))
    (print (+ (.format "Evaluation took: {0}" (- _hy_gensym_end_21 _hy_gensym_start_20)) " seconds"))
    _hy_gensym_result_22)
```

### `defmacro!`を使う例

`with-gensyms`を使うよりよりシンプルに書ける方法として、`defmacro!`を使う方法がある。
これはマクロ定義内で `~g!start` のように、`~g!`を変数名の前に付けることによってそのプリフィクスが付いたシンボルを`gensym`で作ったシンボルで置き換えてくれるというものだ。

```hy
(require hyrule [defmacro!])

(defmacro! timeit [#* body]
  `(do
     (setv ~g!start (time.time))
     (setv ~g!result (do ~@body))
     (setv ~g!end (time.time))
     (print (+ (.format "Evaluation took: {0}" (- ~g!end ~g!start)) " seconds"))
     ~g!result))
```

## TODO: リーダーマクロ
TODO

# TODO: モジュール
TODO

`import`と`require`

`import`と`require`については後述するが、マクロを利用するためには`hy.contrib.walk`モジュールからletマクロをrequireする必要がある。

```hy
(require [hy.contrib.walk [let]])
(let [x 1
      y (+ x 1)]
  (print (.format "x: {0}, y: {1}" x y)))
; x: 1, y: 2
```

# TODO: 型ヒント

Hy1.0から実装されたPythonの型ヒントについての記述

```hy
(setv #^ int x 1)
;; x: int = 1

(defn #^ int add1 [#^ int x]
  (+ x 1))
;; def add1(x: int) -> int:
;;     return x + 1
```

# TODO: スクリプティング

defmainについての記述

defmain_sample.hy
```hy
(require hyrule [defmain])

(defmain [program-name arg1 #* args]
  (print "Welcome to" program-name)
  (print f"arg1: {arg1}, (type arg1): {(type arg1)}")
  (print f"args: {args}, (type args): {(type args)}")
  ;; (print "The answer is" (+ (float argument) 2))
  )

;; $ hy defmain_sample.hy 1
;; Welcome to /Users/wiz/hy/defmain_sample.hy
;; arg1: 1, (type arg1): <class 'str'>
;; args: (), (type args): <class 'tuple'>

;; $ hy defmain_sample.hy 1 2 3
;; Welcome to /Users/wiz/hy/defmain_sample.hy
;; arg1: 1, (type arg1): <class 'str'>
;; args: ('2', '3'), (type args): <class 'tuple'>
```

# TODO: 外部ライブラリ

別の記事に分離する

## Numpy

```hy
(import numpy :as np)

(setv arr (np.array [[1 2 3]
                     [4 5 6]
                     [7 8 9]]))
arr.flags
;; C_CONTIGUOUS : True
;; F_CONTIGUOUS : False
;; OWNDATA : True
;; WRITEABLE : True
;; ALIGNED : True
;; WRITEBACKIFCOPY : False
;; UPDATEIFCOPY : False

arr.ndim ; => 2
arr.size ; => 9
arr.dtype ; => dtype('int64')

;; スカラー倍
(* arr 3)
;; array([[ 3,  6,  9],
;;        [12, 15, 18],
;;        [21, 24, 27]])

;; アダマール積(要素積)
(* arr arr)
;; array([[ 1,  4,  9],
;;        [16, 25, 36],
;;        [49, 64, 81]])

;; 行列積
(np.dot arr arr)
;; array([[ 30,  36,  42],
;;        [ 66,  81,  96],
;;        [102, 126, 150]])

;; 一様乱数で100x100行列を作って行列積を取る
(import numpy.random :as rand)

(setv bigarr1 (rand.rand 1000 1000))
(setv bigarr2 (rand.rand 1000 1000))

(np.dot bigarr1 bigarr2)

;; array([[ 28.38096367,  28.63420504,  28.01482173, ...,  27.26330009,
;;          25.56717227,  27.39401733],
;;        [ 25.26386499,  23.78039895,  22.81641922, ...,  24.37012314,
;;          22.31017675,  22.20606049],
;;        [ 24.79624411,  23.11758526,  24.45533016, ...,  24.47093385,
;;          22.3951194 ,  24.02735416],
;;        ..., 
;;        [ 25.65465691,  25.7403632 ,  23.54518075, ...,  24.36247407,
;;          21.92434498,  23.04834359],
;;        [ 22.37135022,  21.32717967,  21.92101116, ...,  20.93922527,
;;          20.07961519,  20.54109093],
;;        [ 27.50945536,  25.99902791,  25.73058543, ...,  25.71283456,
;;          23.86456424,  25.27311888]])
```