julia の関数について勉強

julia における関数の機微について調べてみた

試した環境

In [1]:
versioninfo()
Julia Version 0.6.2
Commit d386e40 (2017-12-13 18:08 UTC)
Platform Info:
  OS: Linux (x86_64-linux-gnu)
  CPU: Intel(R) Core(TM) i7-6600U CPU @ 2.60GHz
  WORD_SIZE: 64
  BLAS: libopenblas (USE64BITINT DYNAMIC_ARCH NO_AFFINITY Haswell)
  LAPACK: libopenblas64_
  LIBM: libopenlibm
  LLVM: libLLVM-3.9.1 (ORCJIT, skylake)

関数の定義方法は2つ

In [2]:
function func1(x)
   sin(x) # return が無くても最後の式が返値になる
end
Out[2]:
func1 (generic function with 1 method)
In [3]:
func1(x) = sin(x) # 同じシグネチャで再定義すると上書き可能
Out[3]:
func1 (generic function with 1 method)

次の書き方でも func2 を定義できるけれど以下の相違がある

  • 関数自体は無名のまま
  • func2 は変数なので代入が可能
In [4]:
func2 = x -> sin(x)
Out[4]:
(::#1) (generic function with 1 method)
In [5]:
func2 = func1
Out[5]:
func1 (generic function with 1 method)
In [6]:
func2 = 1
Out[6]:
1

関数名は const 扱いで上書きできない

In [7]:
func1 = x -> sin(x)
invalid redefinition of constant func1

Stacktrace:
 [1] include_string(::String, ::String) at ./loading.jl:522

const にすれば代入は不可能になるけど、関数自体は無名のまま

In [8]:
const func3 = x -> sin(x)
Out[8]:
(::#5) (generic function with 1 method)
In [9]:
func2 = func3
Out[9]:
(::#5) (generic function with 1 method)

無名関数を println

無名関数は内部的に #数字 という名前(?)で管理されている?

知ってないと表示を見ても何のことだか分からない

In [10]:
println(x->x)
#7
In [11]:
x->x
Out[11]:
(::#9) (generic function with 1 method)

可変引数

引数やキーワード引数に ... を付けると、Tuple として受け取れる

In [12]:
function test(a, other... ; opt1=0, options...)
    println("a=", a, ", other=", other)
    println("opt1=", opt1, ", options=", options)
end
Out[12]:
test (generic function with 1 method)
In [13]:
test(1, 2, 3, 4; opt1=1, opt2=2, opt3=3)
a=1, other=(2, 3, 4)
opt1=1, options=Any[(:opt2, 2), (:opt3, 3)]

引数の展開

逆に、Tupleや配列に ... を付けて渡すと、展開して渡せる

In [14]:
test((1,2,3)...)
a=1, other=(2, 3)
opt1=0, options=Any[]
In [15]:
test([1,2,3]...)
a=1, other=(2, 3)
opt1=0, options=Any[]
In [16]:
test([1,2]...)
a=1, other=(2,)
opt1=0, options=Any[]

あれ?これは正しいのか?

In [17]:
length((1,))
Out[17]:
1

ああ、(1) だけだと数字の 1 と区別が付かないから、1要素の Tuple は後ろに , を付けてるのか。

キーワード引数も展開して渡せる

In [18]:
test(1;[(:opt1, 1), (:opt2, 2)]...)
a=1, other=()
opt1=1, options=Any[(:opt2, 2)]

引数による分岐

引数型によって異なる関数を呼び出す

ポリモーフィズム的な

In [19]:
test2(a::Int) = println("Int")
Out[19]:
test2 (generic function with 1 method)
In [20]:
test2(s::String) = println("String")
Out[20]:
test2 (generic function with 2 methods)
In [21]:
test2(1)
Int
In [22]:
test2("a")
String

複数マッチするときは型指定の強い方が呼び出される

In [23]:
test2(a::Number) = println("Number")
Out[23]:
test2 (generic function with 3 methods)
In [24]:
test2(1.0)
Number

1 は Number でもあるが Int が採用される

In [25]:
test2(1)
Int
In [26]:
test2(a) = println("Any type")
Out[26]:
test2 (generic function with 4 methods)
In [27]:
test2(true)
Number

ありゃ、Bool は Number なのか。。。

In [28]:
test2([])
Any type
In [29]:
test2(1.0)
Number
In [30]:
test2(1)
Int

可変引数関数との関係

可変引数関数ではない方が優先される

In [31]:
test2(a...) = println("Variable args of any type")
Out[31]:
test2 (generic function with 5 methods)
In [32]:
test2(1)
Int
In [33]:
test2([])
Any type

可変引数でも型指定があればそちらが優先

In [34]:
test2(a::Array...) = print("Variable args of Array")
Out[34]:
test2 (generic function with 6 methods)
In [35]:
test2([])
Variable args of Array
In [36]:
test2(:symbol)
Any type

省略可能引数やばい?!

In [37]:
test2(a, b=false) = println("With omittable b")
Out[37]:
test2 (generic function with 7 methods)
In [38]:
test2(1)
Int
In [39]:
test2(:symbol)
With omittable b

どうしてそっちが呼ばれるの? → 答えは下の方に

キーワード引数付き引数はシグネチャが区別されない

In [40]:
test3(a) = println("Any type")
Out[40]:
test3 (generic function with 1 method)
In [41]:
test3(a; option=false) = println("Any type with option")
Out[41]:
test3 (generic function with 1 method)

2 methods にならない事に注意

In [42]:
test3(1)
Any type with option

これは、キーワード引数付きが優先されたのではなく後から書いたもので上書きされただけ

In [43]:
test3(a) = println("Any type")
Out[43]:
test3 (generic function with 1 method)
In [44]:
test3(1)
Any type
In [45]:
test3(1, option=true)
Any type with option

ではなかった。

優先順位が変わっただけでキーワード引数付きも呼べている。

1 method と書かれている意味は何なのか???

In [46]:
test3(a; option2=false) = println("Any type with option2")
Out[46]:
test3 (generic function with 1 method)
In [47]:
test3(1, option=true)
MethodError: no method matching test3(::Int64; option=true)
Closest candidates are:
  test3(::Any; option2) at In[46]:1 got unsupported keyword argument "option"

Stacktrace:
 [1] (::#kw##test3)(::Array{Any,1}, ::#test3, ::Int64) at ./<missing>:0
 [2] include_string(::String, ::String) at ./loading.jl:522
In [48]:
test3(1, option2=true)
Any type with option2

キーワード引数無しとキーワード引数付きは別シグネチャだけど、異なる名前のキーワード付き同士は同一シグネチャなので、後から定義したもので上書きされてしまうということか。

In [49]:
test3(a; options...) = println("Any type with options")
Out[49]:
test3 (generic function with 1 method)
In [50]:
test3(1, option2=true)
Any type with options

可変キーワード引数付きの優先度が高い?

In [51]:
test3(a; option2=false) = println("Any type with option2")
Out[51]:
test3 (generic function with 1 method)
In [52]:
test3(1, option2=true)
Any type with option2

じゃなくて、後から定義されたものが優先されているだけ?

In [53]:
test3(1, option1=true)
MethodError: no method matching test3(::Int64; option1=true)
Closest candidates are:
  test3(::Any; option2) at In[51]:1 got unsupported keyword argument "option1"

Stacktrace:
 [1] (::#kw##test3)(::Array{Any,1}, ::#test3, ::Int64) at ./<missing>:0
 [2] include_string(::String, ::String) at ./loading.jl:522

いやいや、上書きされてるや

指定キーワード付きと、可変キーワード付きは同一シグネチャとして、互いに上書き対象になってる

もしかして省略可能引数も?

In [54]:
test4(a) = "only a"
test4(a, b=false) = "with omittable b"
Out[54]:
test4 (generic function with 2 methods)
In [55]:
test4("which should be called?")
Out[55]:
"with omittable b"

優先順位が同位なので、後から定義されたものが採用されてるわけだ

だからもう一度「後から」定義してやればそちらが優先される

In [56]:
test4(a) = "only a"
Out[56]:
test4 (generic function with 2 methods)
In [57]:
test4("which should be called?")
Out[57]:
"only a"

省略可能引数持ちとそうでないものとを両方定義するときは順番が重要と覚えよう

ちゃんとこっちも呼べる

In [58]:
test4(1, 2)
Out[58]:
"with omittable b"