Julia フロー制御

フロー制御文は、プログラムによって1つ以上の条件文を設定して実現します。条件が true のときは指定されたプログラムコードを実行し、条件が false のときは他の指定されたコードを実行します。

Julia は多数のフロー制御文を提供しています:

  • 複合式:beginと;。

  • 条件式:if-elseif-elseと?:(三項演算子)。

  • 短絡評価:論理演算子&&(AND) と||(OR)、およびチェーン比較。

  • ループ文:ループ:whileとfor。

  • 例外処理:try-catch、errorとthrow。

  • Task(コルーチン):yieldto。


複合式

begin ... end式は複数の部分式を順番に評価し、最後の部分式の値を返すことができます:

例

julia> z = begin
           x = 1
           y = 2
           x + y
       end
3

これらは非常に短い式なので、1行に簡単に置くことができます。これも;チェーンの由来です:

例

julia> z = (x = 1; y = 2; x + y)
3

実際の使用では、begin コードブロックが複数行である必要はなく、; チェーンが1行である必要もありません:

例

julia> begin x = 1; y = 2; x + y end
3

julia> (x = 1;
        y = 2;
        x + y)
3

条件式

条件式は、ブール式の値に応じてどのコードブロックを実行するかを決定します。

if-elseif-else 構文:

if boolean_expression 1
   /*ブール式1が真の場合に実行*/
elseif boolean_expression 2
   /*ブール式2が真の場合に実行*/
elseif boolean_expression 3
   /*ブール式3が真の場合に実行*/
else
   /*上記の条件がすべて真でない場合に実行*/

1つのif文の後にはオプションのelse if...else文を続けることができ、複数の条件をテストするために使用できます。

if...else if...else 文を使用する際は、以下の点に注意する必要があります:

  • 1つの if の後には0個または1個の else を続けることができ、else はすべての else if の後になければなりません。
  • 1つの if の後には0個以上の else if を続けることができ、else if は else の前になければなりません。
  • いったんある else if が一致すると、他の else if や else はテストされません。

以下は if-elseif-else 条件構文の分析です:

例

if x < y
    println("x is less than y")
elseif x > y
    println("x is greater than y")
else
    println("x is equal to y")
end

式 x < y が true の場合、対応するコードブロックが実行されます。そうでなければ条件式 x > y を判定し、それが true なら対応するコードブロックを実行します。どの式も true でない場合は else コードブロックを実行します。

例

julia> function test(x, y)
           if x < y
               println("x is less than y")
           elseif x > y
               println("x is greater than y")
           else
               println("x is equal to y")
           end
       end
test (generic function with 1 method)

julia> test(1, 2)
x is less than y

julia> test(2, 1)
x is greater than y

julia> test(1, 1)
x is equal to y

三項演算子

三項演算子?:if-elseif-else 構文に似ています:

a ? b : c

に?前の式 a は条件式であり、条件 a が true の場合、三項演算子は : の前の式 b を評価します。条件 a が false の場合、: の後の式 c を実行します。

注意:?と:その周囲のスペースは必須です。a?b:c のような式は有効な三項式ではありません(ただし ? と : の後の改行は許可されています)。

例

julia> x = 1; y = 2;

julia> println(x < y ? "less than" : "not less than")
less than

julia> x = 1; y = 0;

julia> println(x < y ? "less than" : "not less than")
not less than

式 x < y が真の場合、三項演算子全体は文字列 "less than" を実行し、そうでなければ文字列 "not less than" を実行します。

三項演算子をチェーン/ネストして使用する:

例

julia> test(x, y) = println(x < y ? "x is less than y"    :
                            x > y ? "x is greater than y" : "x is equal to y")
test (generic function with 1 method)

julia> test(1, 2)
x is less than y

julia> test(2, 1)
x is greater than y

julia> test(1, 1)
x is equal to y

チェーンで値を渡しやすくするために、演算子は右から左に結合します。

if-elseif-else と同様に、:前後の式は、条件式が true または false の場合にのみ、それぞれ実行されます:

例

julia> v(x) = (println(x); x)
v (generic function with 1 method)

julia> 1 < 2 ? v("yes") : v("no")
yes
"yes"

julia> 1 > 2 ? v("yes") : v("no")
no
"no"

短絡評価

Julia の&&と||演算子はそれぞれ論理"AND"と"OR"操作に対応します。

  • 式a && bにおいて、部分式bの場合にのみaためにtrue実行されます。a が false の場合は直接 false を返します。
  • 式a || bにおいて、部分式bの場合にのみaためにfalse実行されます。a が true の場合は直接 a を返します。

&& の例

julia> isodd(3) && @warn("An odd Number!")
┌ Warning: An odd Number!
└ @ Main REPL[5]:1

julia> isodd(4) && @warn("An odd Number!")
false

|| の例

julia> isodd(3) || @warn("An odd Number!")
true

julia> isodd(4) || @warn("An odd Number!")
┌ Warning: An odd Number!
└ @ Main REPL[8]:1

&& と || はどちらも右辺に依存しますが、&& は || より優先順位が高いです。例を参照してください:

例

julia> t(x) = (println(x); true)
t (generic function with 1 method)

julia> f(x) = (println(x); false)
f (generic function with 1 method)

julia> t(1) && t(2)
1
2
true

julia> t(1) && f(2)
1
2
false

julia> f(1) && t(2)
1
false

julia> f(1) && f(2)
1
false

julia> t(1) || t(2)
1
true

julia> t(1) || f(2)
1
true

julia> f(1) || t(2)
1
2
true

julia> f(1) || f(2)
1
2
false

ループ文

ループ文はwhileとfor2つのキーワードを使用して実装されます。

以下はwhileループの例です:

例

julia> i = 1;

julia> while i <= 5
           println(i)
           global i += 1
       end
1
2
3
4
5

while ループ文は条件式(i <= 5)を実行し、それが true である限り while ループの本体を実行し続けます。条件式が false、つまり i=6 のとき、ループは終了します。

for ループはより便利に使用できます。上記の例を for ループで実装すると次のようになります:

例

julia> for i = 1:5
           println(i)
       end
1
2
3
4
5

ここでの1:5は数字 1, 2, 3, 4, 5 のシーケンスを表す範囲オブジェクトです。

for ループはこれらの値の中で反復し、各変数 i に代入します。

for ループと前述の while ループの非常に重要な違いはスコープ、すなわち変数の可視性です。変数 i が他のスコープで導入されていない場合、for ループ内では、i は for ループの内部でのみ可視であり、外部や後からは可視ではありません。この特性をテストするには、新しい対話式セッションのインスタンスか新しい変数名が必要です:

例

julia> for j = 1:5
           println(j)
       end
1
2
3
4
5

julia> j
ERROR: UndefVarError: j not defined

一般に、for ループの構成要素は任意のコンテナを反復するために使用できます。この場合、= よりも、別の(ただし完全に同じ)キーワードinまたは∈の方がより一般的です。コードがより明確になるからです:

例

julia> for i in [1,4,0]
           println(i)
       end
1
4
0

julia> for s ∈ ["foo","bar","baz"]
           println(s)
       end
foo
bar
baz

便宜上、テスト条件が成立しなくなる前に while ループを終了したり、反復オブジェクトの末尾に到達する前に for ループを停止したりすることがあります。これはキーワードbreakを使用して実現できます:

例

julia> i = 1;

julia> while true
           println(i)
           if i >= 5
               break
           end
           global i += 1
       end
1
2
3
4
5

julia> for j = 1:1000
           println(j)
           if j >= 5
               break
           end
       end
1
2
3
4
5

キーワード break がない場合、上記の while ループは決して自分では終了せず、for ループは 1000 まで反復します。これらのループは break を使用して早期に終了できます。

特定のシナリオでは、現在の反復を直接終了し、すぐに次の反復に進む必要があります。continueキーワードはこの機能を実現するために使用できます:

例

julia> for i = 1:10
           if i % 3 != 0
               continue
           end
           println(i)
       end
3
6
9

これはやや作為的な例です。なぜなら、条件を否定して println 呼び出しを if コードブロック内に置くことで、同じ機能をより簡潔に実現できるからです。実際のアプリケーションでは、continue の後にもっと多くのコードを実行する必要があり、continue を呼び出す場所が複数ある場合があります。

複数のネストされた for ループは1つの外部ループに結合でき、その反復オブジェクトのデカルト積を作成するために使用できます:

例

julia> for i = 1:2, j = 3:4
           println((i, j))
       end
(1, 3)
(1, 4)
(2, 3)
(2, 4)

この構文により、反復変数は通常どおりループ変数を使用してインデックス付けできます。例えばfor i = 1:n, j = 1:iは有効ですが、ループ内で break 文を使用すると、内側のループだけでなく、ネストされたループ全体から抜け出します。内側のループが実行されるたびに、変数(i と j)はその現在の反復変数の値に代入されます。したがって、i への代入はその後の反復には見えません:

例

julia> for i = 1:2, j = 3:4
           println((i, j))
           i = 0
       end
(1, 3)
(1, 4)
(2, 3)
(2, 4)

この例を各変数にキーワード for を使って書き直すと、出力は異なります:2番目と4番目の変数は0を含みます。

zip を使用すると、単一の for ループで複数のコンテナを同時に反復処理できます:

例

julia> for (j, k) in zip([1 2 3], [4 5 6 7])
           println((j,k))
       end
(1, 4)
(2, 5)
(3, 6)

julia> for x in zip(0:15, 100:110, 200:210)
               println(x)
            end
(0, 100, 200)
(1, 101, 201)
(2, 102, 202)
(3, 103, 203)
(4, 104, 204)
(5, 105, 205)
(6, 106, 206)
(7, 107, 207)
(8, 108, 208)
(9, 109, 209)
(10, 110, 210)

// 要素数の不一致を処理する
julia> for x in zip(0:10, 100:115, 200:210)
               println(x)
            end
(0, 100, 200)
(1, 101, 201)
(2, 102, 202)
(3, 103, 203)
(4, 104, 204)
(5, 105, 205)
(6, 106, 206)
(7, 107, 207)
(8, 108, 208)
(9, 109, 209)
(10, 110, 210)

zip を使用すると、渡されたコンテナのサブイテレータを含むタプルであるイテレータが作成されます。 zip イテレータはすべてのサブイテレータを順番に反復処理し、for ループの ii 回目の反復で各サブイテレータの ii 番目の要素を選択します。 いずれかのサブイテレータが使い果たされると、for ループは停止します。

for ステートメントは、カンマを使用して複数のループ条件をネストできます,区切ります:

例

julia> for n in 1:5, m in 1:5
                  @show (n, m)
               end
(n, m) = (1, 1)
(n, m) = (1, 2)
(n, m) = (1, 3)
(n, m) = (1, 4)
(n, m) = (1, 5)
(n, m) = (2, 1)
(n, m) = (2, 2)
(n, m) = (2, 3)
(n, m) = (2, 4)
(n, m) = (2, 5)
(n, m) = (3, 1)
(n, m) = (3, 2)
(n, m) = (3, 3)
(n, m) = (3, 4)
(n, m) = (3, 5)
(n, m) = (4, 1)
(n, m) = (4, 2)
(n, m) = (4, 3)
(n, m) = (4, 4)
(n, m) = (4, 5)
(n, m) = (5, 1)
(n, m) = (5, 2)
(n, m) = (5, 3)
(n, m) = (5, 4)
(n, m) = (5, 5)

内包表記

内包表記は、あるデータシーケンスから別の新しいデータシーケンスの構造体を構築できる独自のデータ処理方法です。

形式は次のとおりです:

[式for変数inリスト]
[out_exp_res for out_exp in input_list]

または

[式for変数inリストif条件]
[out_exp_res for out_exp in input_list if condition]


例

julia> [X^2 for X in 1:5]
5-element Array{Int64,1}:
 1
 4
 9
 16
 25

生成する要素の型を指定することもできます:

例

julia> Complex[X^2 for X in 1:5]
5-element Array{Complex,1}:
 1 + 0im
 4 + 0im
 9 + 0im
 16 + 0im
 25 + 0im

配列の走査

配列の各要素を、要素のインデックス番号を含めて反復処理したい場合があります。

Julia はenumerate(iter)関数を提供します。パラメータiterは反復可能オブジェクトで、この関数はインデックス番号と各インデックス番号に対応する値を生成します。

例

julia> a = ["a", "b", "c"];

julia> for (index, value) in enumerate(a)
           println("$index $value")
       end
1 a
2 b
3 c

julia> arr = rand(0:9, 4, 4)
4×4 Array{Int64,2}:
 7 6 5 8
 8 6 9 4
 6 3 0 7
 2 3 2 4
 
julia> [x for x in enumerate(arr)]
4×4 Array{Tuple{Int64,Int64},2}:
 (1, 7) (5, 6) (9, 5) (13, 8)
 (2, 8) (6, 6) (10, 9) (14, 4)
 (3, 6) (7, 3) (11, 0) (15, 7)
 (4, 2) (8, 3) (12, 2) (16, 4)

例外処理

プログラムの実行中に予期しない条件が発生した場合、関数が呼び出し元に妥当な値を返せないことがあります。このような場合は、予期しない条件でプログラムを終了させ、デバッグ用のエラーメッセージを出力するのが最善です。これにより、開発者はコードを処理しやすくなります。

によってtry / catchステートメントを使用すると、例外を簡単に処理できます。

例えば、以下のコードでは、平方根関数 sqrt が例外を発生させます。 try / catch を使用することで、例外情報を正確に出力できます。

例

julia> try
         sqrt("ten")
       catch e
         println("数字を入力する必要があります")
       end
数字を入力する必要があります

finally 句

状態の変更やファイルのようなリソースを使用するプログラミングでは、コードの終了時に必要なクリーンアップ作業(ファイルを閉じるなど)を行う必要があることがよくあります。例外により一部のコードブロックが正常に終了する前に終了することがあるため、上記の作業が複雑になる可能性があります。 finally キーワードは、コードブロックがどのように終了したかに関係なく、コードブロックの終了時に特定のコードを実行できる方法を提供します。

開かれたファイルが閉じられることを保証する例です:

例

f = open("file")
try
    # operate on file f
finally
    close(f)
end

制御フローが try ブロックを離れると(例えば、return に遭遇した場合や正常終了した場合)、close(f) が実行されます。 try ブロックが例外によって終了した場合、その例外は伝播し続けます。 catch ブロックは try および finally と組み合わせて使用できます。この場合、finally ブロックは catch がエラーを処理した後に実行されます。

throw 関数

throw を使用して例外を明示的に作成できます。

例えば、関数が非負の数に対してのみ定義されている場合、入力パラメータが負の数のときは、throw を使用して DomainError をスローできます。

例

julia> f(x) = x>=0 ? exp(-x) : throw(DomainError(x, "argument must be nonnegative"))
f (generic function with 1 method)

julia> f(1)
0.36787944117144233

julia> f(-1)
ERROR: DomainError with -1:
argument must be nonnegative
Stacktrace:
 [1] f(::Int64) at ./none:1

DomainError の後に括弧を付けない場合、それは例外ではなく例外型であることに注意してください。 Exception オブジェクトを取得するには、それを呼び出す必要があります:

例

julia> typeof(DomainError(nothing)) <: Exception
true

julia> typeof(DomainError) <: Exception
false

さらに、一部の例外型はエラー報告のために1つ以上の引数を受け取ります:

例

julia> throw(UndefVarError(:x))
ERROR: UndefVarError: x not defined

UndefVarError の書き方に倣って、カスタム例外型を使用してこの仕組みを簡単に実装できます:

例

julia> struct MyUndefVarError <: Exception
           var::Symbol
       end

julia> Base.showerror(io::IO, e::MyUndefVarError) = print(io, e.var, " not defined")

Task(コルーチン)

Task(コルーチン)は非局所的なフロー制御を提供し、一時的に中断された計算タスク間の切り替えを可能にします。後の非同期プログラミングの章で詳しく説明します。

その他の拡張