diff options
Diffstat (limited to 'doc/syntax/control_expressions.rdoc')
| -rw-r--r-- | doc/syntax/control_expressions.rdoc | 174 |
1 files changed, 157 insertions, 17 deletions
diff --git a/doc/syntax/control_expressions.rdoc b/doc/syntax/control_expressions.rdoc index 65f7b431e3..3de6cd293f 100644 --- a/doc/syntax/control_expressions.rdoc +++ b/doc/syntax/control_expressions.rdoc @@ -144,7 +144,7 @@ expression. == Modifier +if+ and +unless+ +if+ and +unless+ can also be used to modify an expression. When used as a -modifier the left-hand side is the "then" expression and the right-hand side +modifier the left-hand side is the "then" statement and the right-hand side is the "test" expression: a = 0 @@ -164,7 +164,7 @@ This will print 1. This will print 0. While the modifier and standard versions have both a "test" expression and a -"then" expression, they are not exact transformations of each other due to +"then" statement, they are not exact transformations of each other due to parse order. Here is an example that shows the difference: p a if a = 0.zero? @@ -189,7 +189,7 @@ The same is true for +unless+. The +case+ expression can be used in two ways. The most common way is to compare an object against multiple patterns. The -patterns are matched using the +===+ method which is aliased to +==+ on +patterns are matched using the <tt>===</tt> method which is aliased to <tt>==</tt> on Object. Other classes must override it to give meaningful behavior. See Module#=== and Regexp#=== for examples. @@ -232,7 +232,7 @@ You may use +then+ after the +when+ condition. This is most frequently used to place the body of the +when+ on a single line. case a - when 1, 2 then puts "a is one or two + when 1, 2 then puts "a is one or two" when 3 then puts "a is three" else puts "I don't know what a is" end @@ -255,6 +255,20 @@ Again, the +then+ and +else+ are optional. The result value of a +case+ expression is the last value executed in the expression. +Since Ruby 2.7, +case+ expressions also provide a more powerful +pattern matching feature via the +in+ keyword: + + case {a: 1, b: 2, c: 3} + in a: Integer => m + "matched: #{m}" + else + "not matched" + end + # => "matched: 1" + +The pattern matching syntax is described on +{its own page}[rdoc-ref:syntax/pattern_matching.rdoc]. + == +while+ Loop The +while+ loop executes while a condition is true: @@ -439,11 +453,69 @@ longer true, now you will receive a SyntaxError when you use +retry+ outside of a +rescue+ block. See {Exceptions}[rdoc-ref:syntax/exceptions.rdoc] for proper usage of +retry+. +== Modifier Statements + +Ruby's grammar differentiates between statements and expressions. All +expressions are statements (an expression is a type of statement), but +not all statements are expressions. Some parts of the grammar accept +expressions and not other types of statements, which causes code that +looks similar to be parsed differently. + +For example, when not used as a modifier, +if+, +else+, +while+, +until+, +and +begin+ are expressions (and also statements). However, when +used as a modifier, +if+, +else+, +while+, +until+ and +rescue+ +are statements but not expressions. + + if true; 1 end # expression (and therefore statement) + 1 if true # statement (not expression) + +Statements that are not expressions cannot be used in contexts where an +expression is expected, such as method arguments. + + puts( 1 if true ) #=> SyntaxError + +You can wrap a statement in parentheses to create an expression. + + puts((1 if true)) #=> 1 + +If you put a space between the method name and opening parenthesis, you +do not need two sets of parentheses. + + puts (1 if true) #=> 1, because of optional parentheses for method + +This is because this is parsed similar to a method call without +parentheses. It is equivalent to the following code, without the creation +of a local variable: + + x = (1 if true) + p x + +In a modifier statement, the left-hand side must be a statement and the +right-hand side must be an expression. + +So in <code>a if b rescue c</code>, because <code>b rescue c</code> is a +statement that is not an expression, and therefore is not allowed as the +right-hand side of the +if+ modifier statement, the code is necessarily +parsed as <code>(a if b) rescue c</code>. + +This interacts with operator precedence in such a way that: + + stmt if v = expr rescue x + stmt if v = expr unless x + +are parsed as: + + stmt if v = (expr rescue x) + (stmt if v = expr) unless x + +This is because modifier +rescue+ has higher precedence than <code>=</code>, +and modifier +if+ has lower precedence than <code>=</code>. + == Flip-Flop -The flip-flop is a rarely seen conditional expression. It's primary use is -for processing text from ruby one-line programs used with <code>ruby -n</code> -or <code>ruby -p</code>. +The flip-flop is a slightly special conditional expression. One of its +typical uses is processing text from ruby one-line programs used with +<code>ruby -n</code> or <code>ruby -p</code>. The form of the flip-flop is an expression that indicates when the flip-flop turns on, <code>..</code> (or <code>...</code>), then an expression @@ -452,7 +524,6 @@ will continue to evaluate to +true+, and +false+ when off. Here is an example: - selected = [] 0.upto 10 do |value| @@ -461,15 +532,16 @@ Here is an example: p selected # prints [2, 3, 4, 5, 6, 7, 8] -In the above example, the on condition is <code>n==2</code>. The flip-flop -is initially off (false) for 0 and 1, but becomes on (true) for 2 and remains -on through 8. After 8 it turns off and remains off for 9 and 10. +In the above example, the `on' condition is <code>n==2</code>. The flip-flop +is initially `off' (false) for 0 and 1, but becomes `on' (true) for 2 and +remains `on' through 8. After 8 it turns off and remains `off' for 9 and 10. -The flip-flop must be used inside a conditional such as +if+, +while+, -+unless+, +until+ etc. including the modifier forms. +The flip-flop must be used inside a conditional such as <code>!</code>, +<code>? :</code>, +not+, +if+, +while+, +unless+, +until+ etc. including the +modifier forms. -When you use an inclusive range (<code>..</code>), the off condition is -evaluated when the on condition changes: +When you use an inclusive range (<code>..</code>), the `off' condition is +evaluated when the `on' condition changes: selected = [] @@ -483,7 +555,7 @@ Here, both sides of the flip-flop are evaluated so the flip-flop turns on and off only when +value+ equals 2. Since the flip-flop turned on in the iteration it returns true. -When you use an exclusive range (<code>...</code>), the off condition is +When you use an exclusive range (<code>...</code>), the `off' condition is evaluated on the following iteration: selected = [] @@ -495,5 +567,73 @@ evaluated on the following iteration: p selected # prints [2, 3, 4, 5] Here, the flip-flop turns on when +value+ equals 2, but doesn't turn off on the -same iteration. The off condition isn't evaluated until the following +same iteration. The `off' condition isn't evaluated until the following iteration and +value+ will never be two again. + +== throw/catch + ++throw+ and +catch+ are used to implement non-local control flow in Ruby. They +operate similarly to exceptions, allowing control to pass directly from the +place where +throw+ is called to the place where the matching +catch+ is +called. The main difference between +throw+/+catch+ and the use of exceptions +is that +throw+/+catch+ are designed for expected non-local control flow, +while exceptions are designed for exceptional control flow situations, such +as handling unexpected errors. + +When using +throw+, you provide 1-2 arguments. The first argument is the +value for the matching +catch+. The second argument is optional (defaults to ++nil+), and will be the value that +catch+ returns if there is a matching ++throw+ inside the +catch+ block. If no matching +throw+ method is called +inside a +catch+ block, the +catch+ method returns the return value of the +block passed to it. + + def a(n) + throw :d, :a if n == 0 + b(n) + end + + def b(n) + throw :d, :b if n == 1 + c(n) + end + + def c(n) + throw :d if n == 2 + end + + 4.times.map do |i| + catch(:d) do + a(i) + :default + end + end + # => [:a, :b, nil, :default] + +If the first argument you pass to +throw+ is not handled by a matching ++catch+, an UncaughtThrowError exception will be raised. This is because ++throw+/+catch+ should only be used for expected control flow changes, so +using a value that is not already expected is an error. + ++throw+/+catch+ are implemented as Kernel methods (Kernel#throw and +Kernel#catch), not as keywords. So they are not usable directly if you are +in a BasicObject context. You can use Kernel.throw and Kernel.catch in +this case: + + BasicObject.new.instance_exec do + def a + b + end + + def b + c + end + + def c + ::Kernel.throw :d, :e + end + + result = ::Kernel.catch(:d) do + a + end + result # => :e + end |
