他のLisp系言語をそれなりに知っている人がHyについて簡単に知るためのメモ。随時更新。
変更履歴
- 2024-11-10 Hy 1.0.0対応。旧版の仕様の記述は削除
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がリリースされた。今後は仕様が安定することが期待される
- 一時期1.0のalpha版ということで
変数
変数の導入、設定にはsetv
を使う。
setv
は値を返さないので、設定した値を返すsetx
もある。これはPythonのセイウチ演算子(:=
)に展開される。
(setv x 10)
;; => None
(setx x 10)
;; => 10
let
局所変数を導入するためにはlet
が使える。
スタイルとしてはClojureのlet
に近く、局所変数と値のペアが括弧で括られずフラットに並ぶ。個人的には変数と値の対応が分かりにくくなるので苦手だが、Clojureと同様に,
を入れて見やすくすることもできる。
Clojureのlet
やCommon Lispのlet*
と同じで、1つのlet
の中で前に設定した変数をその後の変数の初期化に利用できる。
Pythonに変換したとき、ユニークな名前の変数に代入しているだけのようである。そのためレキシカルスコープは作らない。
(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要素目以降(引数)が左から右へ順に評価されていき、全ての値が出揃った時点でリスト先頭の関数が適用される(正格評価)。
(+ 1 2 3) ; => 6
Common Lispとは違い、関数と変数で名前空間が分かれておらず、1つの名前空間を共有している。Scheme、Clojure、Pythonと同じだ。
そのため、値として渡ってきた関数を適用するときもfuncall
などは不要で、単純にリストの先頭に置くだけである。
以下は関数を返す関数を定義し、返された関数を5に適用している例。
(defn add-n-generator [n]
(fn [x] (+ x n)))
((add-n-generator 10) 5) ; => 15
#*
, #**
リーダーマクロ
多くのLispには、引数として関数とリストを受け取り、リストを展開して与えられた関数に適用させるためにapply
が用意されている。
Hyではapply
の代わりに#*
リーダーマクロを使う。リストの前に#*
をつけることがでそのリストが展開される。
(+ #*'(1 2 3)) ; => 6
(+ #*[1 2 3]) ; => 6
;; #*を並べて書くこともできる
(+ #*[1 2 3] #*[4 5]) ; => 15
リーダーマクロ #*
をシンボルやタプルの前に付けるときはスペースが必要なことに注意。
(setv lst [1 2 3])
(+ #* lst) ; => 6
;; タプルの前にもスペースが必要
(+ #* #(1 2 3)) ; => 6
同様に、辞書型のデータを展開して関数にキーワード引数として与えることもできる。それには #**
を使う。
(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)
関数定義
関数定義はほぼClojureと同じだが、docstringの位置が異なり、引数リストの後に持ってくる必要がある。なおdocstringはオプショナルである。
例えば、階乗の定義は以下のようになる。他のLisp系言語と同様に、明示的にreturnは必要なく、最後に評価される式が返される。
(defn fact [n]
"docstring for fact"
(if (= n 0)
1
(* n (fact (- n 1)))))
引数オプション
可変長引数
例えば+
のように、可変長引数を取る関数を定義するときには、関数定義の仮引数の前に #*
を置く。
例えば以下の例では、args
が可変長引数のリストになる。#*
にシンボルが続く場合には間にスペースが必要なことに注意する。
(defn my-plus [#* args]
(+ #* args))
(my-plus 1 2 3) ; => 6
オプショナル引数
オプショナルな引数を持つ関数を定義するには、引数リストの中で[optional-parameter default-value]
のようにパラメータ名とデフォルト値のペアを指定する。
これはオプショナル引数とキーワード引数の複合のような指定の仕方になっており、このoptional-parameter
の部分が仮引数、兼キーワード指定子になっている。
オプショナル引数は明示的にキーワードを指定して呼び出すことも、指定しないで呼び出すこともできる。
(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]
デフォルト値は必須。オプショナル引数の位置でデフォルト値を指定しないと構文エラーになる。
(defn opt-arg-func2 [[key1 12] [key2 34] key3]
[key1 key2 key3])
;; =>hy.errors.HySyntaxError: non-default argument follows default argument
任意形式のキーワード引数
仮引数の前に #** を付けることで、形式を定めずにキーワード引数として受け付け、関数内からは辞書型として参照できる。
(defn kwargs-test [#** kwargs]
kwargs)
(kwargs-test :key1 "val1" :key2 "val2")
;; => {"key1" "val1" "key2" "val2"}
ここまで出てきた引数オプションは組み合わせて使うこともできる。
以下は必須引数、オプショナル引数を取りつつ、さらに多くの可変長引数と追加のキーワード引数を受け取る関数の例である。
(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
などの名前付き関数に変換される。これらは変換後の関数名を知っていれば呼び出せてしまうので注意が必要である。
;; 本体部分が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では関数と変数の名前空間が分かれていないので、次のように無名関数を変数に代入することでも関数を定義できる。
(setv add
(fn [x y]
(+ x y)))
(add 2 3) ; => 5
ドキュメント検索
help
で関数のdocstringを参照できる。
(help fact)
; fact(n)
; docstring for fact
Common Lispのapropos
では、現在の処理系にロードされているパッケージを横断してドキュメントを検索できた。これに相当するようなものとしては pydoc
がある。
これはCommon Lispのaproposとは違って、現在ロードされているものではなく、インストールされているパッケージ全体を検索する。そのため若干時間がかかることに注意が必要。
(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
の例
(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
などを使用する。
(import toolz [take])
(list (take 2 (map add1 seq))) ; => [2 3]
;; takeは必要な要素しか評価しない
;; add1(n = 1)
;; add1(n = 2)
filter
の例
(defn even? [n]
(= (% n 2) 0))
(list (filter even? seq)) ; => [2]
reduce
の例
(import functools [reduce])
(defn add [a b]
(+ a b))
(reduce add seq 0) ; => 6
他にitertoolsなども使える。例えば、reduceの溜め込みの途中経過をリストとして取得したければaccumulate
が使える。
(import itertools [accumulate])
(list (accumulate (range 1 11) add)) ; => [1 3 6 10 15 21 28 36 45 55]
制御構造
ブロック
do
doは処理をブロックにまとめる構文で、内部の式を順次評価していき、最後の式の値をdoブロック全体の値として返す。レキシカルスコープは作らない。
Clojureのdo
, Common Lispのprogn
, Schemeのbegin
に相当する。
(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リスト:
'()
- 空文字:
""
(if (or False None 0 0.0 [] #() {} '() "")
"This is true"
"This is false")
; => "This is false"
他のLispと異なり、if
は偽の式が必須であることに注意。3引数でないとは構文エラーになる。
条件が真の場合のみ評価したい場合はwhen
を使う。when
は条件に合わないときはNone
を返す。
(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
としているが、真になるものであれば何でもいい。
(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
の組合せで、条件部が真(偽)のときに本体部分を順に評価していき最後の式の値を返す。
(when True
(print "one")
(print "two")
3)
; one
; two
; => 3
unless
はwhen
の条件部を反転させたもので、多くのLispに存在するマクロである。Hyの場合はHyruleという標準ライブラリパッケージに入っている。
(require hyrule [unless])
(unless False
(print "one")
(print "two")
3)
; one
; two
; => 3
繰り返し
シーケンスに対する繰り返し: for
(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を二重にする方が意図が明確になる分まだいい気がする。
(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のループ変数にも分配束縛のような書き方ができる。
(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
loop
/ recur
マクロ
Pythonは末尾再帰最適化を行なわないので、普通に再帰呼び出しを行うとコールスタックを消費する。末尾再帰呼び出しを単なるループとして処理するために、Clojureのloop/recur
構文と同じものがHyにも用意されている。
再帰呼び出しの関数名がrecur
に固定されていると考えると、Schemeのnamed-let
にも似ている。
これはHyの標準ユーティリティ集であるhyruleに含まれている。マクロなのでrequire
でロードする必要がある。
以下の例はフィボナッチ数の定義を末尾再帰版に直したものである。
(require hyrule [loop])
(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でも同じことはできる。
(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]
例外処理
例外処理にはtry
構文が使える。
Common Lispのhandler-case
のようなものだと思ってよい。
本体部分には複数の式を書くことができ、except
に指定したエラーを捕捉したときの処理を書ける。
(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
節は成否に関わらず通過する。
(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についても同じように使えることが分かる。
(type [1 2 3])
;; => <class 'list'>
(setv list1 [1 2 3])
(get list1 0)
;; => 1
get
に複数の引数を渡すと入れ子になっているリストなどから値を取り出せる。
(setv mat [[1 2 3]
[4 5 6]
[7 8 9]])
(get mat 2 2) ; => 9
(get (get mat 2) 2) ; => 9
リストの特定の要素に代入するには setv と getを組み合わせる。
(setv (get list1 0) 10)
;; Python: list1[0] = 10
list1
;; => [10 2 3]
スライス
スライスは最大3引数の cut
に対応する。
https://qiita.com/yossyyossy/items/0c8dc2ed53466d970fe4
(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
関数によって作れる。
イミュータブルなので要素に値を代入しようとするとエラーになる。
(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 (集合型)
集合型。順序を持たず、要素の重複を許さない。
(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
演算子を使う。
(in 1 #{1 2 3}) ; => True
集合演算
;; 包含関係 (<, <=, >, >=)
(<= #{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を交互に並べる。
参照にはリストと同じで get
を使い、 setv
と組み合わせて代入できる。これは assoc
でも同じことができる。
(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
前述の通り、関数のキーワード引数に展開して与えることもできる。
(defn say [[dog "bark"] [cat "meow"]]
(print "Dog say" dog)
(print "Cat say" cat))
(say #** d)
;; Dog say bark
;; Cat say nyan
クラス
TODO
式 (Expression
)
見た目上は他のLispのリストに近く、クォートで評価から保護しない限り評価規則に従ってプログラムとして評価される。
Hyのコードはこのデータ型から成り立っており、マクロの操作対象のデータとなる。
Pythonのリストとは別のデータ構造として定義されており、データとしてリストが欲しい時は主にPythonのリストの方を使う。
;; リテラル
(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
を自前で定義してみると、以下のようになる。
(defmacro my-when [predicate #* body]
`(if ~predicate
(do ~@body)
None))
式の前に準クォート(バッククォート)を付けると、通常のクォートと同じように式を評価から保護できるが、式の一部分だけを評価して評価結果を埋め込むことができる。
アンクォートは~
、リストを展開するためのスプライシングアンクォートは~@
で、ここはClojureと同じだ。
マクロの展開にはhy.macroexpand-1
が使える。
(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
を作ることができる。
(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)
(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)))
例: gensym
, with-gensyms
を使うケース
TODO
TODO: defmacro!
を使う例
リーダーマクロ
TODO
モジュール
TODO
import
とrequire
import
とrequire
については後述するが、マクロを利用するためにはhy.contrib.walk
モジュールからletマクロをrequireする必要がある。
(require [hy.contrib.walk [let]])
(let [x 1
y (+ x 1)]
(print (.format "x: {0}, y: {1}" x y)))
; x: 1, y: 2
外部ライブラリ
Numpy
(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]])