ジュリア。 文字列とメタプログラミング







若くて有望な汎用言語Juliaの研究を続けています。 今回は、行にさらに注意を払い、メタプログラミングの世界へのti病なステップを開始し、インタープリターにシンボリック操作を実行するように教えます(カットの下には2つの写真しかありませんが、多くの構文上の砂糖があります)









文字列変数は、文字を二重引用符で囲むことによって作成され、単一の文字はchar型の単一の文字に設定されます。 文字列の連結は、乗算「*」を使用して実行されます。







cats = 4 s1 = "How many cats "; s2 = "is too many cats?"; s3 = s1*s2 Out[]: "How many cats is too many cats?"
      
      





つまり、べき乗は次のように機能します。







 s1^3 Out[]: "How many cats How many cats How many cats "
      
      





一方、行にはインデックスが付けられます。







 s1[3] Out[]: 'w': ASCII/Unicode U+0077 (category Ll: Letter, lowercase) s1[5:13] Out[]: "many cats" s1[13:-1:5] Out[]: "stac ynam" s2[end] Out[]: '?': ASCII/Unicode U+003f (category Po: Punctuation, other)
      
      





文字列は、文字列およびその他のタイプから作成できます。







 s4 = string(s3, " - I don't know, but ", cats, " is too few.") #   s4 = "$s3 - I don't know, but $cats is too few." Out[]: "How many cats is too many cats? - I don't know, but 4 is too few."
      
      





武器庫には、要素検索など、多くの便利な機能があります。







 findfirst( isequal('o'), s4 ) Out[]: 4 findlast( isequal('o'), s4 ) Out[]: 26 findnext( isequal('o'), s4, 7 ) #      Out[]: 11
      
      





そのため、 Caesarの暗号を簡単に実装できます







 caesar(X, n) = prod( [x += n for x in X] ) str3 = "    "; caesar(str3, 3) Out[]: "####" str4 = caesar(str3, -32) Out[]: "\0\0\0\0" "Latin letters go before Cyrillic" < str4 < str3 Out[]: true
      
      





ここでは、文字列のすべての文字に対して実行が行われ、各文字のコードにnが追加されます。 prod()関数によって結合されたchar文字の配列が判明します。この関数は、受信した配列のすべての要素の乗算を実装します。







キャラクターコンピューティング



ジュリアには、すでに、シンボリック計算に対するさまざまな程度の準備が整ったパッケージがあります。たとえば、









しかし、どうにかして文字列を接着する方法を学びました。マトリックスでシンボリックアクションを実装してみませんか 2つの異なる配列を追加したいとしましょう。







 m1 = [1 1 "a"; 1 0 1] Out[]: 2×3 Array{Any,2}: 1 1 "a" 1 0 1 m3 = [1 2 "ln(3)"; 2 1 0] Out[]: 2×3 Array{Any,2}: 1 2 "ln(3)" 2 1 0 m1+m3 Out[]: MethodError: no method matching +(::String, ::String) ...
      
      





開始するには、基本的な演算子で遊び心のあるペンを実行します。







 import Base: *, -, +
      
      





C ++でこのような切断が演算子のオーバーロードと呼ばれる場合、ここで既存の関数のメソッドを追加します。







 +(a::String, b::String) = a * "+" * b Out[]: + (generic function with 164 methods)
      
      





150以上のメソッドがありますが、ここにセッションの終了まで存続する別のメソッドがあります。







 m1+m3 Out[]: 2×3 Array{Any,2}: 2 3 "a+ln(3)" 3 1 1
      
      





ここで、乗算をサブポートします。つまり、文字列に数値を乗算する場合を決定します。







 function *(a::String, b::Number) if b == 0 0 else if b == 1 a else "$b" * "*(" * a * ")" end end end Out[]: * (generic function with 344 methods)
      
      





つまり、文字列にゼロを掛けると、ゼロになり、1にすると、同じ文字列になります。また、「*」記号と文字列で数字をブラインドします(文字に不確実性がないように括弧を追加しました)。 個人的には、三項演算子に役立つ関数の1行表示が好きです。







 *(a::String, b::Number) = (b==0) ? 0 : (b==1) ? a : "$b" * "*(" * a * ")" Out[]:
      
      





残りのケースを同様の実行にさらし、次のようなものを取得します。







 import Base: *, -, + +(a::String, b::String) = a * "+" * b *(a::String, b::String) = a * "*(" * b * ")" *(a::String, b::Number) = (b==0) ? 0 : (b==1) ? a : "$b" * "*(" * a * ")" *(b::Number, a::String) = (b==0) ? 0 : (b==1) ? a : "$b" * "*(" * a * ")" +(a::String, b::Number) = (b==0) ? a : a * "+" * "$b" +(b::Number, a::String) = (b==0) ? a : a * "+" * "$b" -(b::Number, a::String) = (b==0) ? "-" * a : "$b" * "-" * a -(a::String, b::Number) = (b==0) ? a : a * "-" * "$b" # m1 = [1 1 "a"; 1 0 1] m2 = [2 0 2; 2 "b" 2; 2 2 2] m1*m2 Out[]: 2×3 Array{Any,2}: "2*(a)+4" "b+2*(a)" "2*(a)+4" 4 2 4
      
      





そして、行列式を計算しましょう! しかし、組み込み関数は複雑であるため、私たちのメソッドは行を含む行列を供給するのに十分ではありません-エラーが発生します。 これは、 Levi-Civitaシンボルを使用して独自にホイップすることを意味します







 ε = zeros(Int, 3,3,3) ε[1,2,3] = ε[2,3,1] = ε[3,1,2] = 1 ε[3,2,1] = ε[1,3,2] = ε[2,1,3] = -1 ε Out[]: 3×3×3 Array{Int64,3}: [:, :, 1] = 0 0 0 0 0 1 0 -1 0 [:, :, 2] = 0 0 -1 0 0 0 1 0 0 [:, :, 3] = 0 1 0 -1 0 0 0 0 0
      
      





混合製品フォーミュラ











\左[ veca vecb vecc\右]= sumijk=13 varepsilonijkaibjck







abcをそれぞれ1行目、2行目、3行目(列)として理解 、3x3行列の行列式を見つけるために使用できます。







 detrmnt(arr) = sum( ε[i,j,k]*arr[1,i]*arr[2,j]*arr[3,k] for i in 1:3, j in 1:3, k in 1:3 ) detrmnt(["a" 2 "b"; 1 0 1; "c" 2 "d"]) Out[]: "2*(c)+2*(b)+2*(-1*(a))+-2*(d)"
      
      





行列の場合はより複雑であり、すべてが非常に自明ではないように見えることは容易に想像できます。多数の括弧と数の乗算があるため、非アクティブ化を行う関数をいくつか作成するとよいでしょう。 これが宿題になります。

さらに、配列の乗算はより複雑です。







 Rx = [1 0 0 0; 0 "cos(ϕ)" "sin(ϕ)" 0; 0 "-sin(ϕ)" "cos(ϕ)" 0; 0 0 0 1]; Rz = ["cos(ϕ)" "sin(ϕ)" 0 0; "-sin(ϕ)" "cos(ϕ)" 0 0; 0 0 1 0; 0 0 0 1]; T = [1 0 0 0; 0 1 0 0; 0 0 1 0; "x0" "y0" "z0" 1]; Rx*Rz Out[]: MethodError: no method matching zero(::String) ...
      
      





エラーが発生します。 明確なビジネス。3次元を超える行列の場合、より高度で複雑なメソッドが使用されますが、その呼び出しは「オーバーロード」しませんでした。 まあ、既存の方法を独自の乗数でカバーします(もちろん、これは、特に内部競合が発生する可能性が高い多かれ少なかれ複雑なプログラムではお勧めできません)。







 function *( a::Array{Any,2}, b::Array{Any,2} ) if size(a)[2] == size(b)[1] res = Array{Any}(undef, size(a)[1], size(b)[2] ) fill!(res, 0) for i = 1:size(a)[1], j = 1:size(b)[2], k = 1:size(a)[2] res[i,j] = res[i,j] + a[i,k]*b[k,j] end res else error("Matrices must have same size mult") end end Out[]: * (generic function with 379 methods)
      
      





これで安全に乗算できます:







 X = Rx*Rz*T Out[]: 4?4 Array{Any,2}: "cos(ϕ)" "sin(ϕ)" 0 0 "cos(ϕ)*(-sin(ϕ))" "cos(ϕ)*(cos(ϕ))" "sin(ϕ)" 0 "-sin(ϕ)*(-sin(ϕ))" "-sin(ϕ)*(cos(ϕ))" "cos(ϕ)" 0 "x0" "y0" "z0" 1
      
      





このマトリックスを念頭に置いてみましょうが、ここでは、最初のステップを開始します







メタプログラミング



引用



ジュリアはメタプログラミングをサポートしています。 これは、値(例:42)ではなく式(例:6 * 7)を扱う記号プログラミングに似ています。 オペレーションをインタープリターに取り込むと、すぐに結果が得られます。これは、見てきたように、次の行を使用して回避できます。







 x = "6*7" Out[]: "6*7"
      
      





文字列は、 parse()を使用して式に変換し、 eval()を使用して評価できます。







 eval(Meta.parse(ans)) Out[]: 42
      
      





なぜこれらすべての困難なのですか? 秘Theは、 がある場合、さまざまな興味深い方法でそれを変更できることです。







 x = replace(x, "*" => "+") eval(Meta.parse(x)) Out[]: 13
      
      





文字列による大騒ぎを避けるために、悲観的な顔演算子が提供されています:()









 y = :(2^8-1) Out[]: :(2^8-1) eval(y) Out[]: 255
      
      





式、関数、コードブロックを「引用」できます...







 quote x = 2 + 2 hypot(x, 5) end Out[]: quote #= In[13]:2 =# x = 2 + 2 #= In[13]:3 =# hypot(x, 5) end :(function mysum(xs) sum = 0 for x in xs sum += x end end) Out[]: :(function mysum(xs) #= In[14]:2 =# sum = 0 #= In[14]:3 =# for x = xs #= In[14]:4 =# sum += x end end)
      
      





ここで、以前に計算されたマトリックスを解析します(前のセクションの後に新しいセッションを開始する方がよいでしょう。私のいオーバーロードはプラグインパッケージと簡単に競合する可能性があります)。







 X = [ "cos(ϕ)" "sin(ϕ)" 0 0; "cos(ϕ)*(-sin(ϕ))" "cos(ϕ)*(cos(ϕ))" "sin(ϕ)" 0; "-sin(ϕ)*(-sin(ϕ))" "-sin(ϕ)*(cos(ϕ))" "cos(ϕ)" 0; "x0" "y0" "z0" 1; ] for i = 1:size(X,1), j = 1:size(X,2) if typeof(X[i,j]) == String X[i,j] = Meta.parse(X[i,j]) end end X Out[]: 4×4 Array{Any,2}: :(cos(ϕ)) :(sin(ϕ)) 0 0 :(cos(ϕ) * -(sin(ϕ))) :(cos(ϕ) * cos(ϕ)) :(sin(ϕ)) 0 :(-(sin(ϕ)) * -(sin(ϕ))) :(-(sin(ϕ)) * cos(ϕ)) :(cos(ϕ)) 0 :x0 :y0 :z0 1
      
      





推測されるように、これは変換のマトリックスであり、すでに3次元座標についてのみこれを整理しています。 特定の値について計算します:







 ϕ = 20*pi/180 x0 = 4 y0 = -0.5 z0 = 1.3 Xtr = [ eval(x) for x in X] Out[]: 4×4 Array{Real,2}: 0.939693 0.34202 0 0 -0.321394 0.883022 0.34202 0 0.116978 -0.321394 0.939693 0 4 -0.5 1.3 1
      
      





この行列は、 X軸とZ軸を中心に次のように回転します 20 circ に転送 \ vec {R} = \ {x0、y0、z0 \}







 x = [-1 -1 1 1 -1 -1 1 1 -1 -1]; y = [-1 -1 -1 -1 -1 1 1 1 1 -1]; z = [1 -1 -1 1 1 1 1 -1 -1 -1] R = [ x' y' z' ones( length(x) ) ] plot(x', y', z', w = 3)
      
      











 R2 = R*Xtr plot!( R2[:,1], R2[:,2], R2[:,3], w = 3)
      
      











表現の木の実



すでに見たように、文字列は「補間」をサポートしているため、小さなコンポーネントから大きな文字列を簡単に作成できます。







 x = "" print("$x $x $x  ...") Out[]:     ...
      
      





引用符付き(引用符)-同じ話:







 x = :(6*7) y = :($x + $x) Out[]: :(6 * 7 + 6 * 7) eval(y) Out[]: 84
      
      





すべての評価のルート



eval()



は、式の値を返すだけではありません。 関数宣言を引用してみましょう:







 ex = :( cats() = println("Meow!") ) cats() Out[]: UndefVarError: cats not defined
      
      





今彼女を復活させます:







 eval(ex) Out[]: cats (generic function with 1 method) cats() Out[]: Meow!
      
      





補間を使用して、関数の定義をその場で作成できます。 実際、すぐに多くの機能を実行できます。







 for name in [:dog, :bird, :mushroom] println(:($name() = println($("I'm $(name)!")))) end Out[]: dog() = begin #= In[27]:2 =# println("I'm dog!") end bird() = begin #= In[27]:2 =# println("I'm bird!") end mushroom() = begin #= In[27]:2 =# println("I'm mushroom!") end for name in [:dog, :bird, :mushroom] eval(:($name() = println($("I'm $(name)!")))) end dog() Out[]: I'm dog! mushroom() Out[]: I'm mushroom!
      
      





これは、APIをラップするときに非常に便利です(たとえば、Cライブラリから、またはHTTPを使用して)。 APIは多くの場合、使用可能な関数のリストを定義するため、それらをキャプチャして、シェル全体を一度に作成できます。 Clang.jl、TensorFlow.jl、または基本線形代数の例を参照してください。







元の罪



より実用的な例を次に示します。 テイラー級数に基づいた次のsin()



関数の定義を考慮してください。











sinx= sumk=1 infty frac1k1+2kx1+2k







 mysin(x) = sum((-1)^k/factorial(1+2*k) * x^(1+2k) for k = 0:5) mysin(0.5), sin(0.5) Out[]: (0.4794255386041834, 0.479425538604203) using BenchmarkTools @benchmark mysin(0.5) Out[]: BenchmarkTools.Trial: memory estimate: 112 bytes allocs estimate: 6 -------------- minimum time: 1.105 μs (0.00% GC) median time: 1.224 μs (0.00% GC) mean time: 1.302 μs (0.00% GC) maximum time: 9.473 μs (0.00% GC) -------------- samples: 10000 evals/sample: 10
      
      





今では、実際よりもはるかに遅くなっています。 その理由は、kを反復処理するためです。これは比較的高価です。 明示的な書き込みははるかに高速です。







 mysin(x) = x - x^3/6 + x^5/120 # + ...
      
      





しかし、これは書くのが面倒で、元のTaylorシリーズのようには見えません。 さらに、苗木のリスクが高い。 2発でウサギを殺す方法はありますか? ジュリアはこのコードを書いてくれませんか? 最初に、+関数のシンボリックバージョンを検討します。







 plus(a, b) = :($a + $b) plus(1, 2) Out[]: :(1 + 2)
      
      





plus()



を使用すると、多くの興味深いことができます。たとえば、シンボリックサム:







 reduce(+, 1:10) Out[]: 55 reduce(plus, 1:10) Out[]: :(((((((((1 + 2) + 3) + 4) + 5) + 6) + 7) + 8) + 9) + 10) eval(ans) Out[]: 55 reduce(plus, [:(x^2), :x, 1]) Out[]: :((x ^ 2 + x) + 1)
      
      





これはパズルの重要な部分を与えてくれますが、何を要約しているのかも把握する必要があります。 kの値を補間する上記のテイラー級数のシンボリックバージョンを作成しましょう。







 k = 2 :($((-1)^k) * x^$(1+2k) / $(factorial(1+2k))) Out[]: :((1 * x ^ 5) / 120)
      
      





これで、コンベア上のように列の要素をリベットできます。







 terms = [:($((-1)^k) * x^$(1+2k) / $(factorial(1+2k))) for k = 0:5] Out[]: 6-element Array{Expr,1}: :((1 * x ^ 1) / 1) :((-1 * x ^ 3) / 6) :((1 * x ^ 5) / 120) :((-1 * x ^ 7) / 5040) :((1 * x ^ 9) / 362880) :((-1 * x ^ 11) / 39916800)
      
      





そしてそれらを要約します。







 reduce(plus, ans) Out[]: :((((((1 * x ^ 1) / 1 + (-1 * x ^ 3) / 6) + (1 * x ^ 5) / 120) + (-1 * x ^ 7) / 5040) + (1 * x ^ 9) / 362880) + (-1 * x ^ 11) / 39916800) :(mysin(x) = $ans) Out[]: :(mysin(x) = begin #= In[52]:1 =# (((((1 * x ^ 1) / 1 + (-1 * x ^ 3) / 6) + (1 * x ^ 5) / 120) + (-1 * x ^ 7) / 5040) + (1 * x ^ 9) / 362880) + (-1 * x ^ 11) / 39916800 end) eval(ans) mysin(0.5), sin(0.5) Out[]: (0.4794255386041834, 0.479425538604203) @benchmark mysin(0.5) Out[]: BenchmarkTools.Trial: memory estimate: 0 bytes allocs estimate: 0 -------------- minimum time: 414.363 ns (0.00% GC) median time: 416.328 ns (0.00% GC) mean time: 431.885 ns (0.00% GC) maximum time: 3.352 μs (0.00% GC) -------------- samples: 10000 evals/sample: 201
      
      





素朴な実装には悪くない! フォトンには150メートルを走らせる時間がなく、サインはすでに計算されています!

これが終了する時間です。 このトピックをさらに整理したい人のために、私はJupyter実行されるインタラクティブなチュートリアル 、そのほとんどがここで使用された翻訳、公式サイトからのビデオコース 、およびドキュメントの対応するセクションをアドバイスます。 新しく到着する場合は、すでに許容量の資料があるため、 ハブを歩き回ることをお勧めします。








All Articles