制御フロー¶
プリミティブ型¶
Nil¶
最も単純な型はNil
です。これには単一の値 nil
のみが存在し、実際の値がないことを表します。
前のレッスンでのString#index
を覚えていますか?これは、検索文字列にサブ文字列が存在しない場合、nil
を返します。インデックスがないため、インデックス位置は存在しません。
p! "Crystal is awesome".index("aw"),
"Crystal is awesome".index("xxxx")
Bool¶
Bool
型には、論理とブール代数の真理値を表す true
と false
の 2 つの値のみが可能です。
p! true, false
ブール値は、プログラムの制御フローを管理するのに特に役立ちます。
ブール代数¶
次の例は、ブール値を使用したブール代数を実装するための演算子を示しています。
a = true
b = false
p! a && b, # conjunction (AND)
a || b, # disjunction (OR)
!a, # negation (NOT)
a != b, # inequivalence (XOR)
a == b # equivalence
a
とb
の値を切り替えて、異なる入力値に対する演算子の動作を確認できます。
真偽性¶
ただし、ブール代数はブール型だけに限定されるわけではありません。すべての値には暗黙の真偽性があります。nil
、false
、および null ポインタ (これは後で説明します) は *偽* です。その他の値 (0
を含む) は *真* です。
上記の例で、true
と false
を他の値 (たとえば "foo"
と nil
) に置き換えてみましょう。
a = "foo"
b = nil
p! a && b, # conjunction (AND)
a || b, # disjunction (OR)
!a, # negation (NOT)
a != b, # inequivalence (XOR)
a == b # equivalence
AND
および OR
演算子は、演算子の真偽値に一致する最初のオペランド値を返します。
p! "foo" && nil,
"foo" && false,
false || "foo",
"bar" || "foo"
NOT
、XOR
、および等価演算子は常にBool
値 (true
または false
) を返します。
制御フロー¶
プログラムのフローを制御するとは、条件に基づいて異なるパスをたどることを意味します。このチュートリアルのこれまでのすべてのプログラムは、一連の式を順番に実行してきました。今、これが変わろうとしています。
条件¶
条件付き句は、条件が満たされた場合にのみ開くゲートの後ろにコードの分岐を配置します。
最も基本的な形式では、キーワード if
の後に条件として機能する式が続きます。式の戻り値が *真* の場合、条件が満たされます。後続のすべての式は、キーワード end
で閉じられるまで、分岐の一部です。
慣例に従い、ネストされた分岐は 2 つのスペースでインデントします。
次の例では、メッセージが Hello
で始まるという条件を満たした場合にのみ、メッセージを出力します。
message = "Hello World"
if message.starts_with?("Hello")
puts "Hello to you, too!"
end
注
厳密に言うと、このプログラムは依然として定義済みの順序で実行されます。固定メッセージは常に一致し、条件を真にします。しかし、ソースコードでメッセージの値を定義しないと仮定しましょう。たとえば、チャットクライアントのように、ユーザー入力から取得することもできます。
メッセージの値が Hello
で始まらない場合、条件付き分岐はスキップされ、プログラムは何も出力しません。
条件式はより複雑になる可能性があります。ブール代数を使用すると、Hello
または Hi
のいずれかを受け入れる条件を作成できます
message = "Hello World"
if message.starts_with?("Hello") || message.starts_with?("Hi")
puts "Hey there!"
end
条件を反転させてみましょう。Hello
で*ない*場合のみ、メッセージを出力します。これは、前の例からのわずかな逸脱にすぎません。否定演算子 (!
) を使用して、条件を反対の式に変えることができます。
message = "Hello World"
if !message.starts_with?("Hello")
puts "I didn't understand that."
end
別の方法としては、if
をキーワード unless
に置き換えることです。これは、ちょうど反対の真偽値を期待します。unless x
は if !x
と同等です。
message = "Hello World"
unless message.starts_with?("Hello")
puts "I didn't understand that."
end
String#index
を使用してサブ文字列を検索し、その位置を強調表示する例を見てみましょう。サブ文字列が見つからない場合は nil
を返すことを覚えていますか? その場合、何も強調表示できません。そのため、インデックスが nil
かどうかを確認する条件を持つ if
句が必要です。.nil?
メソッドが最適です。
str = "Crystal is awesome"
index = str.index("aw")
if !index.nil?
puts str
puts "#{" " * index}^^"
end
コンパイラは、nil
ケースを処理することを強制します。条件を削除するか、条件を true
に変更してみてください。型エラーが表示され、その式で Nil
値を使用できないことを説明します。適切な条件を使用すると、コンパイラは index
が分岐内で nil
になることはなく、数値入力として使用できることを認識します。
ヒント
if !index.nil?
の短い形式は if index
であり、ほぼ同じです。偽の値が nil
か false
かを区別したい場合にのみ違いが生じます。これは、前者条件は false
に一致しますが、後者条件は一致しないためです。
Else¶
プログラムを改良し、メッセージが条件を満たすかどうかに関係なく、両方のケースで反応してみましょう。
これは、否定された条件を持つ 2 つの別々の条件として行うことができます
message = "Hello World"
if message.starts_with?("Hello")
puts "Hello to you, too!"
end
if !message.starts_with?("Hello")
puts "I didn't understand that."
end
これは機能しますが、2 つの欠点があります。条件式 message.starts_with?("Hello")
が 2 回評価されます。これは非効率的です。後で、ある場所で条件を変更した場合 (Hi
も許可する可能性がある)、他の場所も変更するのを忘れる可能性があります。
条件は複数の分岐を持つことができます。代替の分岐は、キーワード else
で示されます。これは、条件が満たされない場合に実行されます。
message = "Hello World"
if message.starts_with?("Hello")
puts "Hello to you, too!"
else
puts "I didn't understand that."
end
その他の分岐¶
プログラムは Hello
にしか反応しませんが、より多くのやり取りが必要です。Bye
にも応答するための分岐を追加しましょう。同じ条件で異なる条件の分岐を持つことができます。これは、別の統合された if
を持つ else
のようなものです。したがって、キーワードは elsif
です
message = "Bye World"
if message.starts_with?("Hello")
puts "Hello to you, too!"
elsif message.starts_with?("Bye")
puts "See you later!"
else
puts "I didn't understand that."
end
else
分岐は、前のどの条件も満たされない場合にのみ実行されます。ただし、常に省略できます。
異なる分岐は相互に排他的であり、条件は上から順に評価されることに注意してください。上記の例では、両方の条件が同時に真になることはないため(メッセージがHello
とBye
の両方で始まることはない)、これは問題になりません。ただし、これを実証するために、排他的ではない別の条件を追加することができます。
message = "Hello Crystal"
if message.starts_with?("Hello")
puts "Hello to you, too!"
elsif message.includes?("Crystal")
puts "Shine bright like a crystal."
end
if message.includes?("Crystal")
puts "Shine bright like a crystal."
elsif message.starts_with?("Hello")
puts "Hello to you, too!"
end
両方の節には同じ条件を持つ分岐がありますが、順序が異なり、動作も異なります。最初に一致した条件によって、どの分岐が実行されるかが選択されます。
ループ¶
このセクションでは、コードの繰り返し実行の基本について説明します。
基本的な機能はwhile
節です。構造はif
節によく似ています。キーワードwhile
が開始を示し、それにループ条件として機能する式が続きます。後続のすべての式は、終了キーワードend
までループの一部です。条件の戻り値が真である限り、ループは繰り返し続けます。
1から10まで数える簡単なプログラムを試してみましょう。
counter = 0
while counter < 10
counter += 1
puts "Counter: #{counter}"
end
while
とend
の間のコードは10回実行されます。現在のカウンター値が出力され、1ずつ増加します。10回目の反復後、counter
の値は10
になり、counter < 10
が失敗してループが終了します。
別の方法として、while
をキーワードuntil
に置き換えることもできます。これは、ちょうど反対の真理値を期待します。until x
はwhile !x
と同等です。
counter = 0
until counter >= 10
counter += 1
puts "Counter: #{counter}"
end
無限ループ¶
ループを扱う場合、ループ条件がいつか偽になるように注意することが重要です。そうしないと、永遠に、または外部からプログラムを停止するまで(たとえば、Ctrl+C、kill
、プラグを抜く、またはハルマゲドンが来るまで)ループが続行されます。
この例では、カウンターをインクリメントしないことは、次のように記述するのと同じです。
while true
puts "Counter: #{counter}"
end
または、条件がcounter > 0
の場合、すべての値で一致します。値は1
からのみ増加するためです。これは技術的には無限ではありません。カウンターが32ビット整数の最大値に達すると、数学エラーで失敗するためです。しかし、概念的には無限ループに似ています。このような論理エラーは見落としやすく、ループ条件を記述するとき、および上記の終了ケースを満たすように注意することが非常に重要です。インデックス変数(この例のcounter
など)の良い習慣は、ループの開始時にインクリメントすることです。これにより、更新を忘れるのが難しくなります。
ヒント
幸いなことに、言語には手動でループを記述する負担を軽減し、有効な終了条件を確保する機能が多数あります。そのうちのいくつかは、後のレッスンで紹介されます。
場合によっては、本当に無限ループを使用することが意図されていることもあります。たとえば、常に接続を待機するサーバーや、ユーザー入力を待機するコマンドプロセッサなどです。その場合は、もちろん明白にする必要があり、複雑で決して失敗しないループ条件に隠すべきではありません。それを表現する最も簡単な方法は、while true
です。条件true
は常に真であるため、ループは無限に繰り返されます。
while true
puts "Hi, what's your name? (hit Enter when done)"
# `gets` returns input from the console
name = gets
puts "Nice to meet you, #{name}."
puts "Now, let's repeat."
end
注
この例は、対話型のプレイグラウンドではありません。プレイグラウンドは自己終了しないプログラムやユーザー入力の処理を処理できないためです。タイムアウトしてエラーを出力するだけです。ただし、ローカルコンパイラでこのコードをコンパイルして実行できます。
プログラムを停止するには、Ctrl+Cを押します。これにより、プロセスに終了を求めるシグナルが送信されます。
スキップと中断¶
途中のいくつかの反復をスキップしたり、特定の条件で反復を完全に停止したりすると便利な場合があります。
ループ本体内のキーワードnext
は、現在の反復に残っている式を無視して、次の反復にスキップします。ループ条件が満たされていない場合、ループは終了し、本体はもう一度実行されません。
counter = 0
while counter < 10
counter += 1
if counter % 3 == 0
next
end
puts "Counter: #{counter}"
end
この例は、puts
式を条件付きで配置することで、next
なしで簡単に記述できた可能性があります。next
の価値は、スキップする必要のあるメソッド本体に多くの式がある場合に明らかになります。
ループ条件は、たとえば複数のステップが必要な場合や、決定する必要がある入力に依存する場合など、計算が難しい場合があります。このような状況では、ループ条件にすべてのロジックを記述するのはあまり実用的ではありません。キーワードbreak
はループ本体のどこでも使用でき、ループ条件に関係なくループから中断するための追加のオプションとして機能します。制御フローは、ループの終了後すぐに継続されます。
counter = 0
while true
counter += 1
puts "Counter: #{counter}"
if counter >= 10
break
end
end
puts "done"