Metaprogramming 入門 === <!-- .element: style="font-size: 280%" --> <!-- .slide: data-background-color="rgba(102,130,223,0.3)" --> 2020/05/09 JuliaTokai \#06 antimon2(後藤 俊介) Note: Julia の Metaprogramming に触れよう! ---- <!-- .slide: data-background-color="rgba(102,130,223,0.3)" --> ## お品書き + お前誰よ? + 簡単なJuliaの紹介 + Metaprogramming --- <!-- .slide: data-background-color="rgba(102,130,223,0.3)" --> # お前誰よ? ---- <!-- .slide: data-background-color="rgba(102,130,223,0.3)" --> ## 自己紹介 + 名前:後藤 俊介 + 所属:**[有限会社 来栖川電算](https://www.kurusugawa.jp)** + コミュニティ:**[JuliaTokai](https://juliatokai.connpass.com/)**, **[機械学習名古屋](https://machine-learning.connpass.com/)**, [Python東海](https://connpass.com/series/292/), Ruby東海, … + 言語:**[Julia](https://julialang.org)**, Python, Scala(勉強中), Ruby, … + ![Twitter](https://i.imgur.com/HqouMIg.png)<!-- .element: class="plain" style="vertical-align:middle;background:transparent" --> [@antimon2](https://twitter.com/antimon2) / ![Facebook](https://i.imgur.com/01nPd37.png)<!-- .element: class="plain" style="vertical-align:middle;background:transparent" --> [antimon2](https://www.facebook.com/antimon2) + ![Github](https://i.imgur.com/yBKtii5.png)<!-- .element: class="plain" style="vertical-align:middle;background:transparent" --> [antimon2](https://github.com/antimon2/) / ![Qiita](https://i.imgur.com/FxHMi64.png)<!-- .element: class="plain" style="vertical-align:middle;background:transparent" --> [@antimon2](http://qiita.com/antimon2) / [<i class="fa fa-file-text"><!-- .element style="font-size:120%" --></i> @antimon2](https://hackmd.io/@antimon2) Note: 今日は(も?)大っぴらに Julia の話ができるっ ---- <!-- .slide: data-background-color="rgba(44,214,221,0.3)" --> [![有限会社来栖川電算](https://i.imgur.com/8Kuhfel.png) https://www.kurusugawa.jp](https://www.kurusugawa.jp) Note: 普段は Python で仕事してますっ --- <!-- .slide: data-background-color="rgba(213,99,92,0.3)" --> # <small>簡単な</small><br>Julia の紹介 Note: Julia の紹介っ ---- <!-- .slide: data-background-color="rgba(213,99,92,0.3)" --> [![Julia](https://raw.githubusercontent.com/JuliaLang/julia-logo-graphics/master/images/julia-logo-color.svg?sanitize=true)<!-- .element: style="background:white;width:80%" -->](https://julialang.org) Note: 実は何気にロゴマイナーチェンジしてますっ ---- <!-- .slide: data-background-color="rgba(213,99,92,0.3)" --> ## Julia とは?(1) + [The Julia Language](https://julialang.org) + 最新 v1.4.1(2020/04/14) + LTS v1.0.5(2019/09/09) + 科学技術計算に強い! + 動作が速い!(LLVM JIT コンパイル) Note: ググるときはなるべく [julialang](https://www.google.co.jp/search?q=julialang) で! ---- <!-- .slide: data-background-color="rgba(213,99,92,0.3)" --> ## Julia とは?(2) > + Rのように中身がぐちゃぐちゃでなく、 > + Rubyのように遅くなく、 > + Lispのように原始的またはエレファントでなく、 > + Prologのように変態的なところはなく、 > + Javaのように硬すぎることはなく、 > + Haskellのように抽象的すぎない > > ほどよい言語である <!-- .element: style="font-size:66%" --> 引用元:http://www.slideshare.net/Nikoriks/julia-28059489/8 <!-- .element: style="font-size:71%" --> ---- <!-- .slide: data-background-color="rgba(213,99,92,0.3)" --> ## Julia とは?(3) > + C のように高速だけど、 Ruby のような動的型付言語である > + Lisp のようにプログラムと同等に扱えるマクロがあって、しかも Matlab のような直感的な数式表現もできる > + Python のように総合的なプログラミングができて、 R のように統計処理も得意で、 Perl のように文字列処理もできて、 Matlab のように線形代数もできて、 shell のように複数のプログラムを組み合わせることもできる > + 超初心者にも習得は簡単で、 超上級者の満足にも応えられる > + インタラクティブにも動作して、コンパイルもできる <!-- .element: style="font-size:50%" --> ([Why We Created Julia](http://julialang.org/blog/2012/02/why-we-created-julia) から抜粋・私訳) <!-- .element: style="font-size:71%" --> Note: いろんな言語の「いいとこどり」言語!ってことでっ ---- <!-- .slide: data-background-color="rgba(213,99,92,0.3)" --> ## 要するに <!-- .element: style="font-size:300%" --> + 動的言語なのに速い! + 文法も覚えやすい! + 数値計算に強い! <!-- .element: style="font-size:180%" --> Note: 機械学習とかにも持って来いっ! ---- <!-- .slide: data-background-color="rgba(213,99,92,0.3)" --> ## 主な機能 <!-- .element: style="font-size:280%" --> + [多重ディスパッチ](https://docs.julialang.org/en/v1/manual/methods/) + [動的型システム](https://docs.julialang.org/en/v1/manual/types/) + [並行・並列処理](https://docs.julialang.org/en/v1/manual/parallel-computing/)、コルーチン + [組込パッケージマネージャ](https://docs.julialang.org/en/v1/stdlib/Pkg/) <!-- .element: style="font-size:160%" --> Note: っ ---- <!-- .slide: data-background-color="rgba(213,99,92,0.3)" --> ## 文法・関数 Note: 以降、ほぼ過去スライドからのコピペ。すっ飛ばして先へ進んで戴いてもOKっ ---- <!-- .slide: data-background-color="rgba(213,99,92,0.3)" --> ### 基本的な演算 ```julia julia> 1 + 2 - 3 * 4 # 四則演算(除算以外) -9 julia> 7 / 5 # `整数 / 整数` の結果は浮動小数 1.4 julia> 7 ÷ 5 # `整数 ÷ 整数` の結果は整数 1 julia> 2 ^ 10 # 冪乗は `^` 1024 julia> 123 & 234 | 345 # 論理積 / 論理和 376 julia> 123 ⊻ 234 # 排他的論理和(==`xor(123, 234)`) 145 ``` <!-- .element: style="font-size:46%" --> Note: 整数同士の除算は実数になりますっ 整数除算演算子 `÷` が別に存在します(Python の `//` 相当)っ また冪乗も(`**` ではなく)`^` ですっ `⊻` は `\xor`+<kbd>Tab</kbd> または `\veebar`+<kbd>Tab</kbd> で変換できますっ ちなみに先ほどの `÷` も `\div`+<kbd>Tab</kbd>で(基本的に ${\rm \TeX}$ の書式)っ ---- <!-- .slide: data-background-color="rgba(213,99,92,0.3)" --> ### 配列 ```julia julia> a = [1, 2, 3, 4, 5] 5-element Array{Int64,1}: 1 2 3 4 5 julia> a[1] # Julia は 1-origin 1 julia> println(a[2:3]) # 範囲指定は両端含む [2, 3] ``` <!-- .element: style="font-size:50%" --> Note: 1-origin であることに注意すればあとは普通の配列っ あと `a:b` は範囲(`Range`)の記法。両端を含む(Ruby の `a..b` と同じ)っ ---- <!-- .slide: data-background-color="rgba(213,99,92,0.3)" --> ### 配列の内包表記 (1) ```julia julia> a = [n^2 for n=1:5] 5-element Array{Int64,1}: 1 4 9 16 25 julia> A = [x+10y for y=1:3, x=1:3] 3×3 Array{Int64,2}: 11 12 13 21 22 23 31 32 33 ``` <!-- .element: style="font-size:50%" --> Note: 内包表記の記法は Python に類似っ かつ、`for` にカンマ区切りで複数のイテレータを渡すことで2次元以上の配列も作成可能っ ---- <!-- .slide: data-background-color="rgba(213,99,92,0.3)" --> ### 配列の内包表記 (2) ```julia julia> [(a,b,c) for c=1:15,b=1:15,a=1:15 if a^2+a*b+b^2==c^2] 6-element Array{Tuple{Int64,Int64,Int64},1}: (3, 5, 7) (5, 3, 7) (6, 10, 14) (7, 8, 13) (8, 7, 13) (10, 6, 14) ``` Note: Python と同様に `if` で条件を指定することも可能っ あと Python と同様、`[○ for ○=○]` を `(○ for ○=○)` と書くと配列ではなくて `Generator` が返りますっ ---- <!-- .slide: data-background-color="rgba(213,99,92,0.3)" --> ### ベクトル ```julia julia> x = [1., 2., 3.]; y = [3., 1., 2.]; julia> x + y # `x .+ y` と書いても同じ(elementwise operation) [4., 3., 5.] julia> x .* y # これは `x * y` と書くとNG [3., 2., 6.] julia> using LinearAlgebra julia> x ⋅ y # 内積(dot積、`dot(x, y)` と書いても同じ) 11.0 julia> x × y # 外積(cross積、`cross(x, y)` と書いても同じ) [1., 7., -5.] ``` <!-- .element: style="font-size:50%" --> Note: Julia では実は1次元配列がベクトルの扱いっ `⋅` は `\cdot`+<kbd>Tab</kbd>、`×` は `\times`+<kbd>Tab</kbd>(これらを利用するには `using LinearAlgebra` 必要)っ あとこれらや先ほどの `÷` や `⊻` などのように、ASCIIの範囲を超えたUnicode文字の演算子(そのほとんどが $\TeX$ 由来)が Julia にはたくさんあります(他には例えば比較演算子の `≤` `≥` や、集合の要素 `∈` や包含関係 `⊆` などなど) ---- <!-- .slide: data-background-color="rgba(213,99,92,0.3)" --> ### 行列 ```julia julia> A = [1 2; 3 4] # この記法は MATLAB/Octave 由来 2×2 Array{Int64,2}: 1 2 3 4 julia> A' # `○'` は転置行列の記法(これも MATLAB/Octave 由来) 2×2 LinearAlgebra.Adjoint{Int64,Array{Int64,2}}: 1 3 2 4 julia> transpose(A) # 正確には転置行列はこっち 2×2 LinearAlgebra.Transpose{Int64,Array{Int64,2}}: 1 3 2 4 ``` <!-- .element: style="font-size:50%" --> Note: Julia では2次元配列が行列の扱いっ あと `○.'` という書式は廃止されました(`transpose(A)` 使ってね)っ ---- <!-- .slide: data-background-color="rgba(213,99,92,0.3)" --> ### 行列の演算 ```julia julia> A = [1 2; 3 4]; B = [3 0; 0 6]; julia> A + B # A .+ B でも同様 2×2 Array{Int64,2}: 4 2 3 10 julia> A * B # matrix multiply 2×2 Array{Int64,2}: 3 12 9 24 julia> A .* B # elementwise multiply 2×2 Array{Int64,2}: 3 0 0 24 ``` <!-- .element: style="font-size:48%" --> Note: 行列は `*` で通常の行列積になりますっこれ便利っ ---- <!-- .slide: data-background-color="rgba(213,99,92,0.3)" --> ### ブロードキャスト ```julia julia> sin(0.1) 0.09983341664682815 julia> sin.([0.1, 0.2, 0.3, 0.4]) 4-element Array{Float64,1}: 0.0998334 0.198669 0.29552 0.389418 julia> [0.1, 0.2, 0.3, 0.4] .^ 2 # => [0.01, 0.04, 0.09, 0.16] ``` <!-- .element: style="font-size:50%" --> Note: 関数名と `(` の間に `.` を置くと、普通の関数を配列に拡張してくれる(ブロードキャスト)っ `.^` のように演算子の前に `.` を書いても同様(先ほど出た `.+` `.*` もブロードキャスト)っ ---- <!-- .slide: data-background-color="rgba(213,99,92,0.3)" --> ### 関数定義 ```julia julia> f(x) = x^2 + 2x - 1 f (generic function with 1 method) julia> f(1) 2 julia> f.(1:5) # => [2, 7, 14, 23, 34] ``` Note: 数学のように直感的な記述で関数を定義可能っ `2x` は `2*x` の省略形、曖昧さがなければリテラルと他の識別子が続く場合などに勝手に乗算と解釈してくれるっ またユーザ定義関数も `.` をつけて自動的にブロードキャスト対応っ ---- <!-- .slide: data-background-color="rgba(213,99,92,0.3)" --> ### 有理数・複素数 ```julia julia> 1//2 == 0.5 true julia> 1//2 - 1//3 1//6 julia> 1im ^ 2 == -1 true julia> (1.0 + 0.5im) * (2.0 - 3.0im) 3.5 - 2.0im ``` Note: 有理数・複素数を標準サポート。 `//` は有理数除算(結果は有理数) `im` は虚数単位。 どちらも四則演算も普通に書けますっ --- <!-- .slide: data-background-color="rgba(96,173,81,0.3)" --> # Metaprogramming Note: メタプログラミングっ ---- <!-- .slide: data-background-color="rgba(96,173,81,0.3)" --> ## Metaprogramming とは? 参考:[Wikipedia: メタプログラミング](https://ja.wikipedia.org/wiki/%E3%83%A1%E3%82%BF%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0) Note: 知らない方のために説明っ ---- <!-- .slide: data-background-color="rgba(96,173,81,0.3)" --> + **プログラムを使ってプログラミングすること** Note: 超簡単に言うとこういうことっ ---- <!-- .slide: data-background-color="rgba(96,173,81,0.3)" --> ### Metaprogramming の例 + メタクラス(オブジェクト指向言語等) + テンプレート(C++, Haskell 等) + マクロ(LISP, Julia 等) Note: メタクラスを利用する言語の例:Ruby 等 マクロは他言語にも色々ある。 --- <!-- .slide: data-background-color="rgba(96,173,81,0.3)" --> ## 関連用語 Note: 先に少し用語解説っ ---- <!-- .slide: data-background-color="rgba(96,173,81,0.3)" --> ### AST(抽象構文木) + (プログラムを)構文解析し、木構造で表したもの + さらに関連用語:S式 Note: "abstract syntax tree" の略っ ---- <!-- .slide: data-background-color="rgba(96,173,81,0.3)" --> ### Homoiconic(同図像性) + ある言語で書かれたプログラムが、その言語自身によってデータとして操作できること + [Why We Created Julia](http://julialang.org/blog/2012/02/why-we-created-julia) の私訳の↓ > Lisp のように **プログラムと同等に扱える** マクロがあって Note: 原文では "with true macros like Lisp," としか書かれていない箇所を意訳しました。 つまり言いたいのは、Julia は Homoiconic っ --- <!-- .slide: data-background-color="rgba(96,173,81,0.3)" --> ## Julia の Metaprogramming Note: っ ---- <!-- .slide: data-background-color="rgba(96,173,81,0.3)" --> ### `Expr` + AST を表現する型 + `:(〜)`(インライン)または `quote 〜 end`(ブロック)の書式で直接記述できる + `head` と `args` の2つのフィールドを持つ + `args` は他の `Expr` や `Symbol` やその他の値を格納する配列 + `dump()` で全体構造を詳細に見ることができる + `Meta.show_sexpr` で S式 の形式で簡易表示できる Note: っ ---- <!-- .slide: data-background-color="rgba(96,173,81,0.3)" --> ```julia julia> ex = :(1 + 2 * 3) :(1 + 2 * 3) julia> typeof(ex) Expr julia> Meta.show_sexpr(ex) (:call, :+, 1, (:call, :*, 2, 3)) julia> dump(ex) Expr head: Symbol call args: Array{Any}((3,)) 1: Symbol + 2: Int64 1 3: Expr head: Symbol call args: Array{Any}((3,)) 1: Symbol * 2: Int64 2 3: Int64 3 ``` Note: っ ---- <!-- .slide: data-background-color="rgba(96,173,81,0.3)" --> ### `Meta.parse()` / `eval()` + `Meta.parse()`:文字列を **解析** し `Expr`(など)に変換する + `eval()`:`Expr`(など)を **評価** し実行結果(値)を得る Note: 他言語(特によくあるスクリプト言語)では、`eval()` 関数が↑の `Meta.parse()` と `eval()` 両方の機能を持つ(解析して評価まで一気に実施する)ものも多いっ ---- <!-- .slide: data-background-color="rgba(96,173,81,0.3)" --> ```julia julia> ex = Meta.parse("x+2*3") :(x + 2 * 3) julia> x = 1; julia> eval(ex) 7 # ← `x + 2 * 3` の評価結果 ``` Note: っ ---- <!-- .slide: data-background-color="rgba(96,173,81,0.3)" --> ### `Expr` の補完 + `$x` 等と記述するとその変数(識別子)の値を展開することが出来る + 文字列の補完と同様 Note: っ ---- <!-- .slide: data-background-color="rgba(96,173,81,0.3)" --> ```julia julia> x, y = 1, 2; julia> ex = :(x + $y * 3) :(x + 2 * 3) # `$y` が展開されて `2` になっている julia> eval(ex) 7 # ← `x + 2 * 3` の評価結果 ``` Note: っ ---- <!-- .slide: data-background-color="rgba(96,173,81,0.3)" --> ### macro + 与えられたコードを別のコードに変換する仕組み + `@〜 …` + 関数と同様な記述で定義可能 Note: っ ---- <!-- .slide: data-background-color="rgba(96,173,81,0.3)" --> ```julia julia> macro ex(ex) QuoteNode(ex) end @ex (macro with 1 method) julia> @ex 1 + 2 * 3 :(1 + 2 * 3) julia> @ex x + $y * 3 :(x + $(Expr(:$, :y)) * 3) # ← `$y` が展開されないことに注目! ``` Note: `@ex` というマクロを定義していますっ これは、それに続く式を `Expr` として返すマクロですっ 「これ何の意味があるの?」は `$y` が展開されないことっ ---- <!-- .slide: data-background-color="rgba(96,173,81,0.3)" --> ### その他 + `@generated function` + 特殊リテラル文字列(`r"〜"`, `big"〜"`, `v"〜"` 等) Note: っ --- <!-- .slide: data-background-color="rgba(96,173,81,0.3)" --> ## お題:メモ化 Note: っ ---- <!-- .slide: data-background-color="rgba(96,173,81,0.3)" --> ### 竹内関数(たらいまわし関数) 参照: [竹内関数 - Wikipedia](https://ja.wikipedia.org/wiki/%E7%AB%B9%E5%86%85%E9%96%A2%E6%95%B0) Note: っ ---- <!-- .slide: data-background-color="rgba(96,173,81,0.3)" --> #### ナイーブ実装 ```julia function tarai_naive(x, y, z) if x ≤ y y else tarai_naive( tarai_naive(x - 1, y, z), tarai_naive(y - 1, z, x), tarai_naive(z - 1, x, y)) end end ``` Note: っ ---- <!-- .slide: data-background-color="rgba(96,173,81,0.3)" --> ```julia julia> @time tarai_naive(12, 6, 0) 0.026209 seconds 12 julia> @time tarai_naive(16, 8, 0) 68.769065 seconds 16 julia> @time tarai_naive(100, 50, 0) # 注意:生きてるうちに返ってこないかも… ``` Note: っ ---- <!-- .slide: data-background-color="rgba(96,173,81,0.3)" --> #### メモ化(手実装) ```julia const TARAI_CACHE = Dict{NTuple{3, Int}, Int}() function tarai_cached(x, y, z) get!(TARAI_CACHE, (x, y, z)) do if x ≤ y y else tarai_cached( tarai_cached(x - 1, y, z), tarai_cached(y - 1, z, x), tarai_cached(z - 1, x, y)) end end end ``` Note: Julia では、`Dict` を用意して `get!(dict, args) do 〜 end` でラップすることで簡易メモ化ができます(制限あり)っ ---- <!-- .slide: data-background-color="rgba(96,173,81,0.3)" --> ```julia julia> @time tarai_cached(12, 6, 0) 0.000013 seconds 12 julia> @time tarai_cached(16, 8, 0) 0.000016 seconds 16 julia> @time tarai_cached(100, 50, 0) 0.003496 seconds (17 allocations: 2.708 MiB) 100 ``` Note: っ ---- <!-- .slide: data-background-color="rgba(96,173,81,0.3)" --> #### `Expr`(AST)比較 Note: ナイーブ実装とメモ化実装の構造を比較してみようっ ---- <!-- .slide: data-background-color="rgba(96,173,81,0.3)" --> ```julia ex_tarai_naive = :(function tarai_naive(x, y, z) if x ≤ y y else tarai_naive(tarai_naive(x - 1, y, z), tarai_naive(y - 1, z, x), tarai_naive(z - 1, x, y)) end end) ex_tarai_cached = :(function tarai_cached(x, y, z) get!(TARAI_CACHE, (x, y, z)) do if x ≤ y y else tarai_cached(tarai_cached(x - 1, y, z), tarai_cached(y - 1, z, x), tarai_cached(z - 1, x, y)) end end end) ``` <!-- .element: style="font-size: 40%" --> Note: っ ---- <!-- .slide: data-background-color="rgba(96,173,81,0.3)" --> ```julia julia> dump(ex_tarai_naive; maxdepth=8) Expr head: Symbol function args: Array{Any}((2,)) 1: Expr head: Symbol call args: Array{Any}((4,)) 1: Symbol tarai_naive 2: Symbol x 3: Symbol y 4: Symbol z 2: Expr head: Symbol block args: Array{Any}((2,)) 1: LineNumberNode line: Int64 2 file: Symbol REPL[11] 2: Expr head: Symbol if args: Array{Any}((3,)) 1: Expr head: Symbol call args: Array{Any}((3,)) 1: Symbol ≤ 2: Symbol x 3: Symbol y 2: Expr head: Symbol block args: Array{Any}((2,)) 1: LineNumberNode 2: Symbol y 3: Expr head: Symbol block args: Array{Any}((2,)) 1: LineNumberNode 2: Expr ``` <!-- .element: style="font-size: 30%" --> Note: っ ---- <!-- .slide: data-background-color="rgba(96,173,81,0.3)" --> ```julia julia> dump(ex_tarai_cached; maxdepth=14) Expr head: Symbol function args: Array{Any}((2,)) 1: Expr head: Symbol call args: Array{Any}((4,)) 1: Symbol tarai_cached 2: Symbol x 3: Symbol y 4: Symbol z 2: Expr head: Symbol block args: Array{Any}((2,)) 1: LineNumberNode line: Int64 2 file: Symbol REPL[14] 2: Expr head: Symbol do args: Array{Any}((2,)) 1: Expr head: Symbol call args: Array{Any}((3,)) 1: Symbol get! 2: Symbol TARAI_CACHE 3: Expr head: Symbol tuple args: Array{Any}((3,)) 1: Symbol x 2: Symbol y 3: Symbol z 2: Expr head: Symbol -> args: Array{Any}((2,)) 1: Expr head: Symbol tuple args: Array{Any}((0,)) 2: Expr head: Symbol block args: Array{Any}((2,)) 1: LineNumberNode line: Int64 3 file: Symbol REPL[14] 2: Expr head: Symbol if args: Array{Any}((3,)) 1: Expr head: Symbol call args: Array{Any}((3,)) 1: Symbol ≤ 2: Symbol x 3: Symbol y 2: Expr head: Symbol block args: Array{Any}((2,)) 1: LineNumberNode 2: Symbol y 3: Expr head: Symbol block args: Array{Any}((2,)) 1: LineNumberNode 2: Expr ``` <!-- .element: style="font-size: 30%" --> Note: っ ---- <!-- .slide: data-background-color="rgba(96,173,81,0.3)" --> ```julia julia> (ex_tarai_naive.head, ex_tarai_naive.args[1], ex_tarai_naive.args[2]) (:function, :(tarai_naive(x, y, z)), quote #= REPL[XX]:2 =# if x ≤ y #= REPL[XX]:3 =# y else #= REPL[XX]:5 =# tarai_naive(tarai_naive(x - 1, y, z), tarai_naive(y - 1, z, x), tarai_naive(z - 1, x, y)) end end) ``` Note: っ ---- <!-- .slide: data-background-color="rgba(96,173,81,0.3)" --> ```graphviz graph AST1 { nodesep=1.0 node [fontname=Courier,style=filled, fillcolor=gray95] edge [arrowsize=0.8] root[label=":function"] root -- arg_1 # root -- arg_2 root -- arg_2 arg_1[label="tarai_naive(x, y, z)", shape=box, style="rounded,filled"] # arg_1[label=":call"] # arg_1 -- arg_1_leaf1 # arg_1 -- arg_1_leaf2 # arg_1 -- arg_1_leaf3 # arg_1 -- arg_1_leaf4 # arg_1_leaf1[label=":tarai_naive"] # arg_1_leaf2[label=":x"] # arg_1_leaf3[label=":y"] # arg_1_leaf4[label=":z"] # arg_2[label=":block"] # arg_2 -- arg_2_1 # arg_2 -- arg_2_2 # arg_2_1[label="#",shape=circle] # arg_2_2[label=":if"] arg_2[label=<<table border="0"><tr><td balign="LEFT">if x ≤ y<br /> y<br /> else<br /> …(略)<br /> end</td></tr></table>>, shape=box, style="rounded,filled",align=LEFT] # arg_2 -- arg_2_1 # arg_2 -- arg_2_2 # arg_2 -- arg_2_3 # arg_2_2_1[label="x ≤ y", shape=box, style="rounded,filled"] # arg_2_2_2[label="y", shape=box, style="rounded,filled"] # arg_2_2_3[label="…", shape=box, style="rounded,filled"] } ``` Note: いろいろ省略してますっ ---- <!-- .slide: data-background-color="rgba(96,173,81,0.3)" --> ```julia julia> (ex_tarai_cached.head, ex_tarai_cached.args[1], ex_tarai_cached.args[2].args[2].args[2].args[2]) (:function, :(tarai_cached(x, y, z)), quote #= REPL[YY]:3 =# if x ≤ y #= REPL[YY]:4 =# y else #= REPL[YY]:6 =# tarai_cached(tarai_cached(x - 1, y, z), tarai_cached(y - 1, z, x), tarai_cached(z - 1, x, y)) end end) ``` Note: っ ---- <!-- .slide: data-background-color="rgba(96,173,81,0.3)" --> ```graphviz graph AST2 { nodesep=1.0 node [fontname=Courier,style=filled, fillcolor=gray95] edge [arrowsize=0.8] root[label=":function"] root -- arg_1 root -- arg_2 arg_1[label="tarai_cached(x, y, z)", shape=box, style="rounded,filled"] arg_2[label="?"] arg_2 -- arg_2_1 arg_2 -- arg_2_2 arg_2_1[label="?"] arg_2_2[label="?"] arg_2_2 -- arg_2_2_1 arg_2_2 -- arg_2_2_2 arg_2_2_1[label="?"] arg_2_2_2[label="?"] arg_2_2_2 -- arg_2_2_2_1 arg_2_2_2 -- arg_2_2_2_2 arg_2_2_2_1[label="?"] arg_2_2_2_2[label=<<table border="0"><tr><td balign="LEFT">if x ≤ y<br /> y<br /> else<br /> …(略)<br /> end</td></tr></table>>, shape=box, style="rounded,filled",align=LEFT] } ``` Note: いろいろ省略してますっ ---- <!-- .slide: data-background-color="rgba(96,173,81,0.3)" --> ##### Points: + `ex_xxx.head` と `ex_xxx.args[1]`(の構造)は同じ + `ex_tarai_naive.args[2]` と `ex_tarai_cached.args[2].args[2].args[2].args[2]`(の構造)は同じ <!-- .element: style="font-size: 80%" --> Note: っ ---- <!-- .slide: data-background-color="rgba(96,173,81,0.3)" --> ```julia julia> ex_tarai_cached.args[2] quote #= REPL[YY]:2 =# get!(TARAI_CACHE, (x, y, z)) do #= REPL[YY]:3 =# if x ≤ y #= REPL[YY]:4 =# y else #= REPL[YY]:6 =# tarai_cached(tarai_cached(x - 1, y, z), tarai_cached(y - 1, z, x), tarai_cached(z - 1, x, y)) end end end ``` <!-- .element: style="font-size: 40%" --> Note: っ ---- <!-- .slide: data-background-color="rgba(96,173,81,0.3)" --> ```julia julia> :(get!(TARAI_CACHE, (x, y, z)) do $(ex_tarai_naive.args[2]) end) :(get!(TARAI_CACHE, (x, y, z)) do #= REPL[ZZ]:2 =# begin #= REPL[XX]:2 =# if x ≤ y #= REPL[XX]:3 =# y else #= REPL[XX]:5 =# tarai_naive(tarai_naive(x - 1, y, z), tarai_naive(y - 1, z, x), tarai_naive(z - 1, x, y)) end end end) ``` <!-- .element: style="font-size: 40%" --> Note: っ ---- <!-- .slide: data-background-color="rgba(96,173,81,0.3)" --> ```graphviz graph AST3 { nodesep=1.0 node [fontname=Courier,style=filled, fillcolor=gray95] edge [arrowsize=0.8] root[label=":function"] root -- arg_1 root -- arg_2 arg_1[label="tarai_cached(x, y, z)", shape=box, style="rounded,filled"] subgraph cluster_1 { label=<<table border="0" cellborder="1" cellspacing="0" cellpadding="0" style="rounded"><tr><td balign="left">get!(TARAI_CACHE, (x, y, z) do <br /> 《外部→》<br /> end</td></tr></table>> fontname=Courier style="rounded,dashed" arg_2[label=":block"] arg_2 -- arg_2_1 arg_2 -- arg_2_2 arg_2_1[label="#"] arg_2_2[label=":do"] arg_2_2 -- arg_2_2_1 arg_2_2 -- arg_2_2_2 arg_2_2_1[label="get!(TARAI_CACHE, (x, y, z))", shape=box, style="rounded,filled"] arg_2_2_2[label=":->"] arg_2_2_2 -- arg_2_2_2_1 } arg_2_2_2 -- arg_2_2_2_2 arg_2_2_2_1[label=":(())"] arg_2_2_2_2[label=<<table border="0"><tr><td balign="LEFT">if x ≤ y<br /> y<br /> else<br /> …(略)<br /> end</td></tr></table>>, shape=box, style="rounded,filled",align=LEFT] } ``` Note: いろいろ省略してますっ ---- <!-- .slide: data-background-color="rgba(96,173,81,0.3)" --> ##### Points: + `ex_tarai_naive.args[2]` を `get!(TARAI_CACHE, (x, y, z)) do 〜 end` でラップしたものと `ex_tarai_cached.args[2]` の構造がほぼ同じ <!-- .element: style="font-size: 80%" --> Note: っ --- <!-- .slide: data-background-color="rgba(96,173,81,0.3)" --> ### 簡易メモ化マクロ Note: っ ---- <!-- .slide: data-background-color="rgba(96,173,81,0.3)" --> ### マクロ実装 ```julia macro simplememoize(ex) @assert Meta.isexpr(ex, :function) fname = ex.args[1].args[1] fname_escaped = esc(fname) fargs = esc.(ex.args[1].args[2:end]) fbody = esc(ex.args[2]) _cache = gensym(Symbol(fname, "_cache")) quote const $_cache = Dict{NTuple{$(length(fargs))}, Any}() function $fname_escaped($(fargs...)) get!($_cache, ($(Expr(:tuple, fargs...)))) do $fbody end end end end ``` <!-- .element: style="font-size: 50%" --> Note: `Meta.isexpr()`:第1引数 `ex` が第2引数で指定した Symbol を `head` として持つ `Expr` かどうかを返す。第3引数に `args` の長さを指定することもできますっ `esc()`:Hygiene処理を抑制して、識別子(`Expr` 中の `head` 以外の `Symbol` 値)をそのまま利用するよう指定する。関数定義などを macro で実現する場合には必須っ `gensym()`:今までに生成されていない一意な名前の `Symbol` を生成する。明示的な hygiene 処理っ ---- <!-- .slide: data-background-color="rgba(96,173,81,0.3)" --> #### マクロ適用確認 ```julia @macroexpand @simplememoize function tarai(x, y, z) if x ≤ y y else tarai(tarai(x - 1, y, z), tarai(y - 1, z, x), tarai(z - 1, x, y)) end end # => ⇓ ``` Note: これが… ---- <!-- .slide: data-background-color="rgba(96,173,81,0.3)" --> ```julia # => ⇓ quote const var"#32###tarai_cache#253" = Dict{NTuple{3}, Any}() function tarai(x, y, z) get!(var"#32###tarai_cache#253", (x, y, z)) do begin if x ≤ y y else tarai(tarai(x - 1, y, z), tarai(y - 1, z, x), tarai(z - 1, x, y)) end end end end end ``` <!-- .element: style="font-size: 50%" --> Note: だいたいこうなるっ (説明のため LineNumberNode 等一部省略してあります) `@macroexpand` は、マクロを展開した結果を `Expr` として返すマクロ。自作マクロのデバッグには必須っ ---- <!-- .slide: data-background-color="rgba(96,173,81,0.3)" --> #### マクロ適用 ```julia @simplememoize function tarai(x, y, z) if x ≤ y y else tarai(tarai(x - 1, y, z), tarai(y - 1, z, x), tarai(z - 1, x, y)) end end # => tarai (generic function with 1 method) ``` Note: っ ---- <!-- .slide: data-background-color="rgba(96,173,81,0.3)" --> ```julia julia> @time tarai(12, 6, 0) 0.000052 seconds (767 allocations: 42.016 KiB) 12 julia> @time tarai(16, 8, 0) 0.000031 seconds (191 allocations: 5.969 KiB) 16 julia> @time tarai(100, 50, 0) 0.005187 seconds (85.71 k allocations: 3.580 MiB) 100 ``` Note: 手実装よりパフォーマンス悪いのは、`Dict` が最適化されていないためっ --- <!-- .slide: data-background-color="rgba(170,121,193,0.3)" --> # まとめ ---- <!-- .slide: data-background-color="rgba(170,121,193,0.3)" --> + Macro 使いこなせるとかっこいい! + Metaprogramming 分かると楽しい! + Julia 楽しい! Note: っ --- <!-- .slide: data-background-color="rgba(170,121,193,0.3)" --> # リンク ---- <!-- .slide: data-background-color="rgba(170,121,193,0.3)" --> ## 実験Note + [![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/antimon2/SimpleMemoizeSample.jl/master?filepath=example%2Ftarai_sample.ipynb) ---- <!-- .slide: data-background-color="rgba(170,121,193,0.3)" --> ## 参考リンク/書籍 + [Wikipedia: メタプログラミング](https://ja.wikipedia.org/wiki/%E3%83%A1%E3%82%BF%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0) + [Metaprogramming](https://docs.julialang.org/en/v1/manual/metaprogramming/) - [Julia Documentation](https://docs.julialang.org/en/v1) + [1から始める Juliaプログラミング | コロナ社](https://www.coronasha.co.jp/np/isbn/9784339029055/) pp.82-95 --- <!-- .slide: data-background-color="rgba(170,121,193,0.3)" --> ご清聴ありがとうございます。 Note: ご清聴ありがとうございますっ!
{"metaMigratedAt":"2023-06-15T07:16:54.894Z","metaMigratedFrom":"YAML","title":"Metaprogramming 入門","breaks":true,"slideOptions":"{\"transition\":\"slide\",\"theme\":\"league\"}","contributors":"[{\"id\":\"80062a4b-8dad-49ac-95bf-848ce0686e9e\",\"add\":23393,\"del\":18035}]"}
    1121 views