using TensorOperations, BenchmarkTools function expr_Aj_Gprod(A, G, N) is = Symbol.(:i, 1:N) js = Symbol.(:j, 1:N) ks = circshift(is, -1) Gs = Expr.(:ref, Ref(G), 1:N) Gsijk = Expr.(:ref, Gs, is, js, ks) Gprod = Expr(:call, :*, Gsijk...) Aj = Expr(:ref, A, js...) Aj, Gprod end expr_Aj_Gprod(:A, :G, 4) macro multr(G, N) A, G = esc(gensym(:A)), esc(G) #Core.println("macro: ", G) # Generated function では初回実行時にのみ呼び出されている Aj, Gprod = expr_Aj_Gprod(A, G, N) :(@tensor $Aj := $Gprod) end @macroexpand1 @multr(H, 4) # 分岐があらかじめ列挙できる程度の数ならば良い選択肢だと思う for N in 2:10 @eval multr_pre(G, ::Val{$N}) = @multr(G, $N) end multr_pre(G) = multr_pre(G, Val(length(G))) # 呼ばれてから構文を評価する function multr_live(G, ::Val{N}) where N eval( quote let G = $G # 配列を変数に入れ直したほうがわずかに速い @multr(G, $N) end end ) end multr_live(G) = multr_live(G, Val(length(G))) # @multr が gensym を含むため pure とは言えない?(引数の型からコンパイル済みのコードへの対応に注目した場合) # TensorOperations.jl でも gensym を使っているようなので、 generated function を使うなら気にしても仕方がない? @generated function multr_gen(G, ::Val{N}) where N quote #Core.println("generated: ", G) # シンボルではなく、配列が渡されている @multr(G, $N) end end multr_gen(G) = multr_gen(G, Val(length(G))) genG(is = [3, 4, 5, 6], js = [2, 3, 4, 5]) = randn.(is, js, circshift(is, -1)) # ミス検出のため変数名を G ではなく H にしている H = genG(); @multr(H, 4) == multr_pre(H) == multr_live(H) == multr_gen(H) @benchmark @multr($H, 4) @benchmark multr_pre($H) @benchmark multr_live($H) # 遅い @benchmark multr_gen($H)