Julia

Juliaの関数について

Juliaの関数についてDocumentを読んだまとめ

英語も言語仕様も理解が未熟なため、間違いの指摘や補足等いただけますと幸いです:bow_tone1:

v0.6.1のドキュメントです。

関数(function)

関数を定義する一般的な書き方はこれ

julia> function f(x, y)
           x + y
       end
f (generic function with 1 method)

上記と等価な代入形式(assignment form)はこう

julia> f(x, y) = x + y
f (generic function with 1 method)

呼び出しは伝統的な丸括弧で

julia> f(2, 3)
5

呼び出しの括弧をつけない場合は関数オブジェクトとして参照されます

julia> g = f
f (generic function with 1 method)

julia> g(2, 3)
5

引数の振る舞い

pass-by-sharingと呼ばれる慣習に従ってます。
引数として関数に渡されるときにはその値をコピーしません。
引数は新しい変数として振る舞いますが、渡された値と同一のものが参照されます。

配列のようなミュータブルな値の変更は、呼び出し元に反映されます。

returnキーワード

デフォルトでは最後の式の評価を返すが、returnキーワードを用いると続く式を即時に返します。

julia> function h(x, y)
           return x * y
           x + y
       end
h (generic function with 1 method)

演算子も関数

演算子は特殊な構文をサポートする関数です。

julia> 1 + 2 + 3
6

julia> +(1, 2, 3)
6

julia> g = +
+ (generic function with 180 methods)

julia> g(1, 2, 3)
6

無名関数(Anonymous Function)

Juliaの関数は第一級オブジェクトです。
無名で作ることもできます。

julia> x -> x^2 + 2x - 1
(::#1) (generic function with 1 method)

julia> function (x)
           x^2 + 2x - 1
       end
(::#3) (generic function with 1 method)

mapで無名関数を用いる例

julia> map(x -> x^2 + 2x - 1, [1, 3 ,-1])
3-element Array{Int64,1}:
  2
 14
 -2

すぐに使える目的の関数がない時、無名関数で一時的に使い関数オブジェクトを名前無しでかんたんに作ることができます。

複数の引数の場合は(x,y,z)->2x+y-z、引数なしの場合は()->3のようにかけば良いです。
引数無しの関数はちょいとおかしいですが、遅延評価したい時につかえます。

複数の返り値(Multiple Returns Values)

複数の返り値を実現するために、タプルを用いますが、()は必要ないです。

julia> function foo(a, b)
           a+b, a*b
       end
foo (generic function with 1 method)

julia> foo(2, 3)
(5, 6)

タプルの値はかんたんに分解することができます。

julia> x, y = foo(2, 3)
(5, 6)

julia> x
5

julia> y
6

可変引数(varargs)

任意の数の引数を取りたいことがあります。

julia> bar(a, b, x...) = (a, b, x)
bar (generic function with 1 method)

julia> bar(1, 2)
(1, 2, ())

julia> bar(1, 2, 3)
(1, 2, (3,))

julia> bar(1, 2, 3, 4)
(1, 2, (3, 4))

逆に繰り返し可能な集合を展開することもできます。

julia> x = (3, 4)
(3, 4)

julia> bar(1, 2, x...)
(1, 2, (3, 4))

julia> x = (1, 2, 3, 4)
(1, 2, 3, 4)

julia> bar(x...)
(1, 2, (3, 4))

省略可能な引数(デフォルト引数)

組み込み関数のparseを例にとってみます。

function parse(T, num, base=10)
    ###
end

Tの型に文字列のnumbase進数でパースします。
baseの引数がデフォルト10で与えられます。

julia> parse(Int, "12", 10)
12

julia> parse(Int, "12", 3)
5

julia> parse(Int, "12")
12

キーワード引数

引数の順番によらず値を渡すことができます。
plotを例にとってみます。

function plot(x, y; style="solid", width=1, color="black")
    ###
end

呼び出す時は

plot(x, y, width=2)

というふうになります。
型を明示的に与えるにはこう

function f(;x::Int64=1)

可変引数にも対応します。

function f(x; y=0, kwargs...)
    ###
end

この場合kwargs(key, value)のタプルで与えられます。
plot(x, y, (:width, 2))といったかたちになります。

plot(x, y; options..., width=2)optionswidthが含まれている状態でも可能です。そのばあい直接キーワード引数で指定されているものが優先されます。

デフォルト値の評価範囲

省略可能引数とキーワード引数の僅かな違いはデフォルト値の評価の仕方にあります。
省略可能引数のデフォルトの式が評価される時、範囲は直前の引数までで、キーワード引数の場合はすべての引数が対象になります。

function f(x, a=b, b=1)
    ###
end

この場合bは外のスコープでのbが参照されます。

function f(x; a=b, b=1)
    ###
end

この場合bは引数内の値を参照します(a=bb=1が続くのでUndefVarErrorになります)。

引数内でのDoブロックの構文

関数を引数として用いるのは強力なテクニックですが、複数行にわたるときなんかはやっかいです。

julia> map(x->begin
               if x < 0 && iseven(x)
                   return 0
               elseif x == 0
                   return 1
               else
                   return x
               end
           end,
           [-2, 0, 3])
3-element Array{Int64,1}:
 0
 1
 3

doを使うときれいに書けます

julia> map([-2, 0, 3]) do x
           if x < 0 && iseven(x)
               return 0
           elseif x == 0
               return 1
           else
               return x
           end
       end
3-element Array{Int64,1}:
 0
 1
 3

do xの構文に続く無名関数は関数の第一引数として渡されます。

ベクトル関数でのドット構文

技術計算のプログラミング言語ではよくベクトルを扱う関数が用意されています。
通常の値のxに対応するf(x)を用いて、f(A)から配列Aの各値に繰り返し適用した新たな配列を得ることができます。
このような構文はデータ処理に便利ですが、高いパフォーマンスが要求されます。
もしループが遅ければ関数のベクトル版は低級言語でかかれた早いライブラリを実行できます。
Juliaでは高いパフォーマンスを必要とせずに、自身のループを書くことで本当の価値を得られます。
しかも便利なまま。

Juliaではf.(A)という構文で配列Aの要素ごとに関数fを適用することができます。

julia> A = [1.0, 2.0, 3.0]
3-element Array{Float64,1}:
 1.0
 2.0
 3.0

julia> sin.(A)
3-element Array{Float64,1}:
 0.841471
 0.909297
 0.14112

julia> sin(A)
WARNING: sin(x::AbstractArray{T}) where T <: Number is deprecated, use sin.(x) instead.
Stacktrace:
 [1] depwarn(::String, ::Symbol) at ./deprecated.jl:70
 [2] sin(::Array{Float64,1}) at ./deprecated.jl:57
 [3] eval(::Module, ::Any) at ./boot.jl:235
 [4] eval_user_input(::Any, ::Base.REPL.REPLBackend) at ./REPL.jl:66
 [5] macro expansion at ./REPL.jl:97 [inlined]
 [6] (::Base.REPL.##1#2{Base.REPL.REPLBackend})() at ./event.jl:73
while loading no file, in expression starting on line 0
3-element Array{Float64,1}:
 0.841471
 0.909297
 0.14112

(そのままも受け取ってくれるものの非推奨のwarningがでます。)

mapをつかえば.を使わずできて同様の効果が得られるものの、その方法ではベクトル化が必要な関数にするかどうかを先んじて決める必要があります。

一般的に、f.(args...)broadcast(f, args...)と等価であり、複数の配列に対する操作ができます。

julia> f(x,y) = 3x + 4y;

julia> A = [1.0, 2.0, 3.0];

julia> B = [4.0, 5.0, 6.0];

julia> f.(pi, A)
3-element Array{Float64,1}:
 13.4248
 17.4248
 21.4248

julia> f.(A, B)
3-element Array{Float64,1}:
 19.0
 26.0
 33.0

さらに、ネストした場合にも一つのbroadcastのループに結合した形で呼び出します。
sin.(cos.(X))broadcast(x->sin(cos(x)), X)です。
(1回のX分のループで良い!)
(従来のベクトル関数を扱う言語ではこのループが別々に行われます。tmp=cos(X),sin(tmp)のように)

sin.(sort(cos.(X)))のように間に非.な関数がはいるとマージされません。

ベクトル関数によって出力される配列が事前割り振りされていると、最大限の効果を発揮できます。
繰り返しの呼び出しで新しい配列を割り当てをしないですみます。

一番かんたんな書き方がこう

X .= sin.(Y)
broadcast!(sin, X, Y)

と等価になります。

式の中で演算や関数呼び出しが増えると.を入れるのがめんどくさいし、読みにくくなります。
そんなときは@.マクロを使うと全ての関数呼び出し、演算、代入が.バージョンになります。

julia> Y = [1.0, 2.0, 3.0, 4.0];

julia> X = similar(Y); # 出力先の配列を事前割当

julia> @. X = sin(cos(Y)) # X .= sin.(cos.(Y)) と等価
4-element Array{Float64,1}:
  0.514395
 -0.404239
 -0.836022
 -0.608083