Julia - #undef か否かを調べる

環境

  • Julia 1.3.0

配列の要素が undef か否かを調べる時は isassigned を使う

Arrays · The Julia Language

julia> v = Vector{Any}(undef, 5)
5-element Array{Any,1}:
 #undef
 #undef
 #undef
 #undef
 #undef

julia> v[1:3] .= 'a':'c'
3-element view(::Array{Any,1}, 1:3) with eltype Any:
 'a'
 'b'
 'c'

julia> v[1:3] = 'a':'c'
'a':1:'c'

julia> v
5-element Array{Any,1}:
    'a'
    'b'
    'c'
 #undef
 #undef

julia> v = Vector{Any}(undef, 5)
5-element Array{Any,1}:
 #undef
 #undef
 #undef
 #undef
 #undef

julia> v[1:3] = 'a':'c'
'a':1:'c'

julia> v
5-element Array{Any,1}:
    'a'
    'b'
    'c'
 #undef
 #undef

julia> isassigned(v, 1)
true

julia> isassigned(v, 2)
true

julia> isassigned(v, 3)
true

julia> isassigned(v, 4)
false

julia> isassigned(v, 5)
false

Julia - ドット . を使って method を呼ぶ

約1年前に全く Julia っぽくない邪道な遊びをしていたなということをふと思い出した。
微妙なメモだけは残っていたので供養しておく。

これから紹介する方法は遊びであって本気にされると困ります。

環境

  • Julia 1.3.0

getproperty

v.method という呼び方は getproperty が呼ばれているので、この関数をオーバーロードすると v.length() のような他の言語では一般的だけれども、全然 Julia っぽくない書き方を一応実現することはできる。

https://docs.julialang.org/en/v1/base/base/#Base.getproperty

julia> function Base.getproperty(v::Vector, s::Symbol)
           s == :length && return () -> length(v)
           s == :reshape && return function (a,b)
               return reshape(v, a,b)
           end
       end

julia> v = rand(10)
10-element Array{Float64,1}:
 0.8267378196342272 
 0.6176403952129186 
 0.5046041927146574 
 0.8758407174025733 
 0.6085707115128745 
 0.9317736835188135 
 0.40242234842524804
 0.9733691132606714 
 0.5724142410241722 
 0.568618356534722  

julia> v.length()
10

julia> v.reshape(2,5)
2×5 Array{Float64,2}:
 0.826738  0.504604  0.608571  0.402422  0.572414
 0.61764   0.875841  0.931774  0.973369  0.568618

PyCall.jlでPyTorch使ってDeep Learningする - Qiita の記事の方法を使うとより汎用的になる。(一部走るように修正した) ただし、全体的にものすごく動作がもっさりする。

julia> function Base.getproperty(obj, s::Symbol)
           if isdefined(obj, s)
               getfield(obj, s)
           else
               eval(quote (args...) ->  $(s)($obj, args...) end)
           end
       end
WARNING: Method definition getproperty(Any, Symbol) in module Base at Base.jl:20 overwritten in module Main at REPL[1]:2.

julia> v = rand(6)
6-element Array{Float64,1}:
 0.06120994308448946 
 0.5256925985282832  
 0.9023581914503764  
 0.8468689107200875  
 0.019238205610359937
 0.6605465949011493  

julia> function myfunc(v::Vector, a)
           return v .+ a
       end
myfunc (generic function with 1 method)

julia> v.myfunc(10)
6-element Array{Float64,1}:
 10.061209943084489
 10.525692598528284
 10.902358191450377
 10.846868910720087
 10.01923820561036 
 10.660546594901149

julia> v.size()
(6,)

struct

class のある他言語のように、struct 定義時に method を生やそうと思えば一応生やせる。

julia> mutable struct Hoge
           v::Array
           length::Function
           size::Function
           reshape::Function
           
           function Hoge(v)
               x = new()
               x.v = v
               x.length = () -> length(x.v)
               x.size = () -> size(x.v)
               x.reshape = (a, b) -> reshape(x.v, a, b)
               return x
           end
       end

julia> x = Hoge([1, 2, 3])
Hoge([1, 2, 3], var"#3#5"{Hoge}(Hoge(#= circular reference @-2 =#)), var"#4#6"{Hoge}(Hoge(#= circular reference @-2 =#)))

julia> x.length()
3

julia> x.size()
(3,)

julia> x.v = rand(10, 5)
10×5 Array{Float64,2}:
 0.0844862  0.842631  0.339725    0.954259   0.194565  
 0.470892   0.521036  0.00714723  0.846754   0.524923  
 0.870488   0.545857  0.545821    0.827207   0.974655  
 0.29028    0.876719  0.637267    0.997776   0.462824  
 0.925254   0.858045  0.53476     0.794868   0.330684  
 0.711127   0.318063  0.0518006   0.0385135  0.00460019
 0.930399   0.979754  0.0512777   0.0573049  0.412405  
 0.627587   0.281032  0.115321    0.194771   0.87389   
 0.646853   0.140648  0.906595    0.0133067  0.158983  
 0.539785   0.133379  0.0629104   0.143179   0.659137

julia> x.length()
50

julia> x.size()
(10, 5)

julia> x.reshape(5, 10)
5×10 Array{Float64,2}:
 0.0844862  0.711127  0.842631  0.318063  0.339725    0.0518006  0.954259  0.0385135  0.194565  0.00460019
 0.470892   0.930399  0.521036  0.979754  0.00714723  0.0512777  0.846754  0.0573049  0.524923  0.412405  
 0.870488   0.627587  0.545857  0.281032  0.545821    0.115321   0.827207  0.194771   0.974655  0.87389   
 0.29028    0.646853  0.876719  0.140648  0.637267    0.906595   0.997776  0.0133067  0.462824  0.158983  
 0.925254   0.539785  0.858045  0.133379  0.53476     0.0629104  0.794868  0.143179   0.330684  0.659137

Julia - retry 機構

環境

  • Julia 1.3.0

retry

ある処理がエラーになってしまったとき、時間を置いて再度実行したらエラーが出ないことはよく有ることです。例えばサーバーにうまく接続できないとか。 そんなときに使うのが retry 機構です。retry 機構を使うことでエラー時の再実行を容易にすることが出来ます。

Julia の retry 機構は以下のようなインターフェースになっています。

retry(f;  delays=ExponentialBackOff(), check=nothing) -> Function

関数 f が実際に実行される処理、delays が再実行する間隔と回数、check はどのエラーによって再実行するかをコントロールするときに指定するもの(っぽい)です。

https://docs.julialang.org/en/v1/base/base/#Base.retry

retry() の返り値は第1引数の関数 f に retry 機構を付与したものと考えればよいと思います。

以下の例は1秒間隔で最大3回関数 f を実行

julia> x = 0
0

julia> function f()
           global x += 1
           if x < 3
               println("x = ", x)
               error()
           else
               return println("Hello World")
           end
       end
f (generic function with 1 method)

julia> retry(f, delays=[1, 1, 1])()
x = 1
x = 2
Hello World

do ... end ブロックを使って以下のようにも書けます。ある処理の一部分に retry 機構を付けたい場合はこちらのほうが便利かもしれません。

julia> retry(delays=[1, 1, 1]) do
           global x += 1
           if x < 3
               println("x = ", x)
               error()
           else
               return println("Hello World")
           end
       end()   # retry の返り値は関数なので最後に `([args...])` を忘れずに
x = 1
x = 2
Hello World

実用的にはこんな感じでしょうか。

retry(delays=[1, 1, 1]) do url
    HTTP.request("GET", url)
end("http://httpbin.org/ip")

Exponential BackOff

待つ間隔を指数関数的に増やす場合は ExponentialBackOff を使います。

ExponentialBackOff(; n=1, first_delay=0.05, max_delay=10.0, factor=5.0, jitter=0.1)

https://docs.julialang.org/en/v1/base/base/#Base.ExponentialBackOff

n が retry する回数で、k回目の retry 時の待ち時間は以下の式で与えられます。

{ \displaystyle
t = \min
\left(
  \text{first_delay} \times \text{factor}^{( (k-1) + \mathrm{uniform}(-\text{jitter},~\text{jitter}) )}, \text{max_delay} \right)
}

ここで {\mathrm{uniform}(a,b)}区間 {[a, b)} の一様乱数 { \displaystyle \mathrm{uniform}(a,b) = (b - a) \times \mathrm{rand}() + a}

AWS でのエラーの再試行とエクスポネンシャルバックオフ - AWS 全般のリファレンス

julia> ExponentialBackOff(n=5, first_delay=0.05, max_delay=10, jitter=0) |> collect
5-element Array{Float64,1}:
  0.05
  0.25
  1.25
  6.25
 10.0 

julia> [0.05 * 5^(i-1) for i in 1:5]
5-element Array{Float64,1}:
  0.05
  0.25
  1.25
  6.25
 31.25

Document の但し書きについて

公式 Document を見ると

Before Julia 1.2 this signature was restricted to f::Function.

という但し書きがあります。ようするに retry の定義が 1.2 より前では

retry(f::Function;  delays=ExponentialBackOff(), check=nothing) -> Function

だったということですが、

Return an anonymous function that calls function f.

とあるのだから、f::Function で一見問題ない気がします。

issue や PR をちゃんと見ていないので正確ではありませんが、おそらく Function-like objects などを取れるようになったということを言いたいのかなと私は思いました。

https://docs.julialang.org/en/v1/manual/methods/#Function-like-objects-1

julia> struct Polynomial{R}
           coeffs::Vector{R}
       end

julia> function (p::Polynomial)(x)
           v = p.coeffs[end]
           for i = (length(p.coeffs)-1):-1:1
               v = v*x + p.coeffs[i]
           end
           return v
       end

julia> (p::Polynomial)() = p(5)

julia> p = Polynomial([1,10,100])
Polynomial{Int64}([1, 10, 100])

# Julia 1.2 未満
julia> retry(p)
ERROR: MethodError: no method matching retry(::Polynomial{Int64})
Closest candidates are:
  retry(::Function; delays, check) at error.jl:198
Stacktrace:
 [1] top-level scope at none:0

# Julia 1.2 以降
julia> retry(p)
#50 (generic function with 1 method)

Julia - ソースコードを読んでみる

gdb を使って Julia のソースコードを追ってみる

環境

gdbソースコードリーディング

gdb で追う場合ソースコードからビルドする必要がある。

$ wget https://github.com/JuliaLang/julia/releases/download/v1.3.0/julia-1.3.0-full.tar.gz
$ tar xvf julia-1.3.0-full.tar.gz
$ cd julia-1.3.0
$ export JULIA_BUILD_MODE=debug
$ make
$ ./julia
               _
   _       _ _(_)_     |  Documentation: https://docs.julialang.org
  (_)     | (_) (_)    |
   _ _   _| |_  __ _   |  Type "?" for help, "]?" for Pkg help.
  | | | | | | |/ _` |  |
  | | |_| | | | (_| |  |  Version 1.3.0 (2019-11-26)
 _/ |\__'_|_|_|\__'_|  |  
|__/                   |

julia> 

新規にターミナルを開いて

$ ps aux | grep julia
arch       14398  3.4  2.4 537996 195032 pts/0   Sl+  20:20   0:00 ./julia

$ gdb -p 14398      # 起動中の Julia の pid に attach

(gdb) b jl_arrayset # 適当なところに breakpoint を入れる
Breakpoint 1 at 0x7f1f71a38e70: file /home/arch/.local/julia/julia-1.3.0/src/array.c, line 568.
(gdb) c
Continuing.

# REPL に戻って適当に x = [1, 2] とか打ってみる

Thread 1 "julia" hit Breakpoint 1, jl_arrayset (a=0x7f1f63aa77f0, rhs=0x7f1f611ee050, i=i@entry=0)
    at /home/arch/.local/julia/julia-1.3.0/src/array.c:568
568    {

(gdb) n
802        return jl_svec_data(t)[i];

(gdb) 
571        if (eltype != (jl_value_t*)jl_any_type) {

(gdb) p jl_any_type
$1 = (jl_datatype_t *) 0x7fd8d9b601d0 <jl_system_image_data+16>

(gdb) ptype jl_datatype_t
type = struct _jl_datatype_t {
    jl_typename_t *name;
    struct _jl_datatype_t *super;
    jl_svec_t *parameters;
    jl_svec_t *types;
    jl_svec_t *names;
    jl_value_t *instance;
    const jl_datatype_layout_t *layout;
    int32_t size;
    int32_t ninitialized;
    uint32_t uid;
    uint8_t abstract;
    uint8_t mutabl;
    uint8_t hasfreetypevars;
    uint8_t isconcretetype;
    uint8_t isdispatchtuple;
    uint8_t isbitstype;
    uint8_t zeroinit;
    uint8_t isinlinealloc;
    uint8_t has_concrete_subtype;
    void *struct_decl;
    void *ditype;
}
 
(gdb) ptype jl_svec_t
type = struct {
    size_t length;
}

(gdb) p jl_any_type->parameters->length
$20 = 0

その時その時にどんな値が入ってきているのかわかって面白い。

gdbptrace: operation not permitted. と出たら以下を打つ

$ sudo su -
# echo 0 > /proc/sys/kernel/yama/ptrace_scope

stackoverflow.com

お試し用の Dockerfile

github.com

Julia - Channel コードサンプル

環境

  • Julia 1.2.0

Golang で Channel を使ってようやく Julia の Channel の動きを理解できた気がする。

capa = 10
c = Channel(capa)

function f(c)
    for i in 1:10
        put!(c, i)
    end
end

function g(c)
    for i in c
        sleep(0.5)
        println("From g: ", i)
    end
    println("Finish")
end

@async f(c)
@async g(c)
close(c)

f:id:goropikarikun:20191119232525g:plain

for i in c
    sleep(0.5)
    println("From g: ", i)
end

の部分は channel が close されるまでずっと回り続ける。

Julia には Go の WaitGroup のようなものはないっぽい。Threads.atomic_add!() などを駆使すれば似たようなことを実現できそうな気はする。