diff options
Diffstat (limited to 'doc/syntax')
| -rw-r--r-- | doc/syntax/assignment.rdoc | 62 | ||||
| -rw-r--r-- | doc/syntax/calling_methods.rdoc | 211 | ||||
| -rw-r--r-- | doc/syntax/comments.rdoc | 253 | ||||
| -rw-r--r-- | doc/syntax/control_expressions.rdoc | 195 | ||||
| -rw-r--r-- | doc/syntax/exceptions.rdoc | 24 | ||||
| -rw-r--r-- | doc/syntax/keywords.rdoc | 162 | ||||
| -rw-r--r-- | doc/syntax/layout.rdoc | 118 | ||||
| -rw-r--r-- | doc/syntax/literals.rdoc | 494 | ||||
| -rw-r--r-- | doc/syntax/methods.rdoc | 329 | ||||
| -rw-r--r-- | doc/syntax/miscellaneous.rdoc | 41 | ||||
| -rw-r--r-- | doc/syntax/modules_and_classes.rdoc | 79 | ||||
| -rw-r--r-- | doc/syntax/operators.rdoc | 75 | ||||
| -rw-r--r-- | doc/syntax/pattern_matching.rdoc | 528 | ||||
| -rw-r--r-- | doc/syntax/precedence.rdoc | 6 | ||||
| -rw-r--r-- | doc/syntax/refinements.rdoc | 113 |
15 files changed, 2426 insertions, 264 deletions
diff --git a/doc/syntax/assignment.rdoc b/doc/syntax/assignment.rdoc index 7424d4885f..3988f82e5f 100644 --- a/doc/syntax/assignment.rdoc +++ b/doc/syntax/assignment.rdoc @@ -1,6 +1,6 @@ = Assignment -In Ruby assignment uses the <code>=</code> (equals sign) character. This +In Ruby, assignment uses the <code>=</code> (equals sign) character. This example assigns the number five to the local variable +v+: v = 5 @@ -8,6 +8,9 @@ example assigns the number five to the local variable +v+: Assignment creates a local variable if the variable was not previously referenced. +An assignment expression result is always the assigned value, including +{assignment methods}[rdoc-ref:@Assignment+Methods]. + == Local Variable Names A local variable name must start with a lowercase US-ASCII letter or a @@ -92,8 +95,9 @@ Now any reference to +big_calculation+ is considered a local variable and will be cached. To call the method, use <code>self.big_calculation</code>. You can force a method call by using empty argument parentheses as shown above -or by using an explicit receiver like <code>self.</code>. Using an explicit -receiver may raise a NameError if the method's visibility is not public. +or by using an explicit receiver like <code>self</code>. Using an explicit +receiver may raise a NameError if the method's visibility is not public or the +receiver is the literal <code>self</code>. Another commonly confusing case is when using a modifier +if+: @@ -103,12 +107,34 @@ Rather than printing "true" you receive a NameError, "undefined local variable or method `a'". Since ruby parses the bare +a+ left of the +if+ first and has not yet seen an assignment to +a+ it assumes you wish to call a method. Ruby then sees the assignment to +a+ and will assume you are referencing a local -method. +variable. The confusion comes from the out-of-order execution of the expression. First the local variable is assigned-to then you attempt to call a nonexistent method. +== Local Variables and eval + +Using +eval+ to evaluate Ruby code will allow access to local variables defined +in the same scope, even if the local variables are not defined until after the +call to +eval+. However, local variables defined inside the call to +eval+ +will not be reflected in the surrounding scope. Inside the call to +eval+, +local variables defined in the surrounding scope and local variables defined +inside the call to +eval+ will be accessible. However, you will not be able +to access local variables defined in previous or subsequent calls to +eval+ in +the same scope. Consider each +eval+ call a separate nested scope. Example: + + def m + eval "bar = 1" + lvs = eval "baz = 2; ary = [local_variables, foo, baz]; x = 2; ary" + eval "quux = 3" + foo = 1 + lvs << local_variables + end + + m + # => [[:baz, :ary, :x, :lvs, :foo], nil, 2, [:lvs, :foo]] + == Instance Variables Instance variables are shared across all methods for the same object. @@ -136,9 +162,7 @@ Here is an example of instance variable usage: p object1.value # prints "some value" p object2.value # prints "other value" -An uninitialized instance variable has a value of +nil+. If you run Ruby with -warnings enabled you will get a warning when accessing an uninitialized -instance variable. +An uninitialized instance variable has a value of +nil+. The +value+ method has access to the value set by the +initialize+ method, but only for the same object. @@ -255,7 +279,7 @@ An uninitialized global variable has a value of +nil+. Ruby has some special globals that behave differently depending on context such as the regular expression match variables or that have a side-effect when -assigned to. See the {global variables documentation}[rdoc-ref:globals.rdoc] +assigned to. See the {global variables documentation}[rdoc-ref:language/globals.md] for details. == Assignment Methods @@ -279,7 +303,7 @@ to an instance variable most people use Module#attr_accessor: end When using method assignment you must always have a receiver. If you do not -have a receiver Ruby assumes you are assigning to a local variable: +have a receiver, Ruby assumes you are assigning to a local variable: class C attr_accessor :value @@ -319,6 +343,9 @@ This prints: local_variables: @value: 42 +Note that the value returned by an assignment method is ignored whatever, +since an assignment expression result is always the assignment value. + == Abbreviated Assignment You can mix several of the operators and assignment. To add 1 to an object @@ -354,7 +381,7 @@ Here is an example: p a # prints 1 -Note that these two operators behave more like <code>a || a = 0<code> than +Note that these two operators behave more like <code>a || a = 0</code> than <code>a = a || 0</code>. == Implicit Array Assignment @@ -374,7 +401,11 @@ assigning. This is similar to multiple assignment: p a # prints [1, 2, 3] -You can splat anywhere in the left-hand side of the assignment: + b = *1 + + p b # prints [1] + +You can splat anywhere in the right-hand side of the assignment: a = 1, *[2, 3] @@ -382,7 +413,7 @@ You can splat anywhere in the left-hand side of the assignment: == Multiple Assignment -You can assign multiple values on the left-hand side to multiple variables: +You can assign multiple values on the right-hand side to multiple variables: a, b = 1, 2 @@ -408,8 +439,8 @@ You can use multiple assignment to swap two values in-place: p new_value: new_value, old_value: old_value # prints {:new_value=>1, :old_value=>2} -If you have more values on the left hand side of the assignment than variables -on the right hand side the extra values are ignored: +If you have more values on the right hand side of the assignment than variables +on the left hand side, the extra values are ignored: a, b = 1, 2, 3 @@ -422,7 +453,7 @@ the assignment. p a: a, b: b # prints {:a=>1, :b=>[2, 3]} -The <code>*</code> can appear anywhere on the right-hand side: +The <code>*</code> can appear anywhere on the left-hand side: *a, b = 1, 2, 3 @@ -452,4 +483,3 @@ Since each decomposition is considered its own multiple assignment you can use p a: a, b: b, c: c, d: d # prints {:a=>1, :b=>2, :c=>[3, 4], :d=>[5, 6]} - diff --git a/doc/syntax/calling_methods.rdoc b/doc/syntax/calling_methods.rdoc index 79c0de59dc..76babcc3dc 100644 --- a/doc/syntax/calling_methods.rdoc +++ b/doc/syntax/calling_methods.rdoc @@ -30,6 +30,63 @@ NoMethodError. You may also use <code>::</code> to designate a receiver, but this is rarely used due to the potential for confusion with <code>::</code> for namespaces. +=== Chaining Method Calls + +You can "chain" method calls by immediately following one method call with another. + +This example chains methods Array#append and Array#compact: + + a = [:foo, 'bar', 2] + a1 = [:baz, nil, :bam, nil] + a2 = a.append(*a1).compact + a2 # => [:foo, "bar", 2, :baz, :bam] + +Details: + +- First method <tt>merge</tt> creates a copy of <tt>a</tt>, + appends (separately) each element of <tt>a1</tt> to the copy, and returns + [:foo, "bar", 2, :baz, nil, :bam, nil] +- Chained method <tt>compact</tt> creates a copy of that return value, + removes its <tt>nil</tt>-valued entries, and returns + [:foo, "bar", 2, :baz, :bam] + +You can chain methods that are in different classes. +This example chains methods Hash#to_a and Array#reverse: + + h = {foo: 0, bar: 1, baz: 2} + h.to_a.reverse # => [[:baz, 2], [:bar, 1], [:foo, 0]] + +Details: + +- First method Hash#to_a converts <tt>a</tt> to an \Array, and returns + [[:foo, 0], [:bar, 1], [:baz, 2]] +- Chained method Array#reverse creates copy of that return value, + reverses it, and returns + [[:baz, 2], [:bar, 1], [:foo, 0]] + +=== Safe Navigation Operator + +<code>&.</code>, called "safe navigation operator", allows to skip method call +when receiver is +nil+. It returns +nil+ and doesn't evaluate method's arguments +if the call is skipped. + + REGEX = /(ruby) is (\w+)/i + "Ruby is awesome!".match(REGEX).values_at(1, 2) + # => ["Ruby", "awesome"] + "Python is fascinating!".match(REGEX).values_at(1, 2) + # NoMethodError: undefined method `values_at' for nil:NilClass + "Python is fascinating!".match(REGEX)&.values_at(1, 2) + # => nil + +This allows to easily chain methods which could return empty value. Note that +<code>&.</code> skips only one next call, so for a longer chain it is necessary +to add operator on each level: + + "Python is fascinating!".match(REGEX)&.values_at(1, 2).join(' - ') + # NoMethodError: undefined method `join' for nil:NilClass + "Python is fascinating!".match(REGEX)&.values_at(1, 2)&.join(' - ') + # => nil + == Arguments There are three types of arguments when sending a message, the positional @@ -62,7 +119,7 @@ The positional arguments for the message follow the method name: my_method(argument1, argument2) -In many cases parenthesis are not necessary when sending a message: +In many cases, parenthesis are not necessary when sending a message: my_method argument1, argument2 @@ -75,7 +132,7 @@ to: If the method definition has a <code>*argument</code> extra positional arguments will be assigned to +argument+ in the method as an Array. -If the method definition doesn't include keyword arguments the keyword or +If the method definition doesn't include keyword arguments, the keyword or hash-type arguments are assigned as a single hash to the last argument: def my_method(options) @@ -84,7 +141,7 @@ hash-type arguments are assigned as a single hash to the last argument: my_method('a' => 1, b: 2) # prints: {'a'=>1, :b=>2} -If too many positional arguments are given an ArgumentError is raised. +If too many positional arguments are given, an ArgumentError is raised. === Default Positional Arguments @@ -149,9 +206,28 @@ like positional arguments: my_method(positional1, keyword1: value1, keyword2: value2) Any keyword arguments not given will use the default value from the method -definition. If a keyword argument is given that the method did not list an +definition. If a keyword argument is given that the method did not list, +and the method definition does not accept arbitrary keyword arguments, an ArgumentError will be raised. +Keyword argument value can be omitted, meaning the value will be fetched +from the context by the name of the key + + keyword1 = 'some value' + my_method(positional1, keyword1:) + # ...is the same as + my_method(positional1, keyword1: keyword1) + +Be aware that when method parenthesis are omitted, too, the parsing order might +be unexpected: + + my_method positional1, keyword1: + + some_other_expression + + # ...is actually parsed as + my_method(positional1, keyword1: some_other_expression) + === Block Argument The block argument sends a closure from the calling scope to the method. @@ -215,16 +291,16 @@ override local arguments outside the block in the caller's scope: This prints: hello main this is block - place is world + place is: world So the +place+ variable in the block is not the same +place+ variable as outside the block. Removing <code>; place</code> from the block arguments gives this result: hello main this is block - place is block + place is: block -=== Array to Arguments Conversion +=== Unpacking Positional Arguments Given the following method: @@ -246,32 +322,81 @@ Both are equivalent to: my_method(1, 2, 3) -If the method accepts keyword arguments the splat operator will convert a hash -at the end of the array into keyword arguments: +The <code>*</code> unpacking operator can be applied to any object, not only +arrays. If the object responds to a <code>#to_a</code> method, this method +is called, and is expected to return an Array, and elements of this array are passed +as separate positional arguments: - def my_method(a, b, c: 3) + class Name + def initialize(name) + @name = name + end + + def to_a = @name.split(' ') end - arguments = [1, 2, { c: 4 }] - my_method(*arguments) + name = Name.new('Jane Doe') + p(*name) + # prints separate values: + # Jane + # Doe + +If the object doesn't have a <code>#to_a</code> method, the object itself is passed +as one argument: + + class Name + def initialize(name) + @name = name + end + end + + name = Name.new('Jane Doe') + p(*name) + # Prints the object itself: + # #<Name:0x00007f9d07bca650 @name="Jane Doe"> + +This allows to handle one or many arguments polymorphically. Note also that <tt>*nil</tt> +is unpacked to an empty list of arguments, so conditional unpacking is possible: + + my_method(*(some_arguments if some_condition?)) + +If <code>#to_a</code> method exists and does not return an Array, it would be an +error on unpacking: + + class Name + def initialize(name) + @name = name + end + + def to_a = @name + end + + name = Name.new('Jane Doe') + p(*name) + # can't convert Name to Array (Name#to_a gives String) (TypeError) You may also use the <code>**</code> (described next) to convert a Hash into keyword arguments. If the number of objects in the Array do not match the number of arguments for -the method an ArgumentError will be raised. +the method, an ArgumentError will be raised. If the splat operator comes first in the call, parentheses must be used to -avoid a warning. +avoid an ambiguity of interpretation as an unpacking operator or multiplication +operator. In this case, Ruby issues a warning in verbose mode: + + my_method *arguments # warning: '*' interpreted as argument prefix + my_method(*arguments) # no warning -=== Hash to Keyword Arguments Conversion +=== Unpacking Keyword Arguments Given the following method: def my_method(first: 1, second: 2, third: 3) end -You can turn a Hash into keyword arguments with the <code>**</code> operator: +You can turn a Hash into keyword arguments with the <code>**</code> +(keyword splat) operator: arguments = { first: 3, second: 4, third: 5 } my_method(**arguments) @@ -285,8 +410,38 @@ Both are equivalent to: my_method(first: 3, second: 4, third: 5) -If the method definition uses <code>**</code> to gather arbitrary keyword -arguments they will not be gathered by <code>*</code>: +The <code>**</code> unpacking operator can be applied to any object, not only +hashes. If the object responds to a <code>#to_hash</code> method, this method +is called, and is expected to return an Hash, and elements of this hash are passed +as keyword arguments: + + class Name + def initialize(name) + @name = name + end + + def to_hash = {first: @name.split(' ').first, last: @name.split(' ').last} + end + + name = Name.new('Jane Doe') + p(**name) + # Prints: {name: "Jane", last: "Doe"} + +Unlike <code>*</code> operator, <code>**</code> raises an error when used on an +object that doesn't respond to <code>#to_hash</code>. The one exception is ++nil+, which doesn't explicitly define this method, but is still allowed to +be used in <code>**</code> unpacking, not adding any keyword arguments. + +Again, this allows for conditional unpacking: + + my_method(some: params, **(some_extra_params if pass_extra_params?)) + +Like <code>*</code> operator, <code>**</code> raises an error when the object responds +to <code>#to_hash</code>, but it doesn't return a Hash. + +If the method definition uses the keyword splat operator to +gather arbitrary keyword arguments, they will not be gathered +by <code>*</code>: def my_method(*a, **kw) p arguments: a, keywords: kw @@ -296,10 +451,7 @@ arguments they will not be gathered by <code>*</code>: Prints: - {:arguments=>[1, 2], :keywords=>{"3"=>4, :five=>6}} - -Unlike the splat operator described above the <code>**</code> operator has no -commonly recognized name. + {:arguments=>[1, 2], :keywords=>{'3'=>4, :five=>6}} === Proc to Block Conversion @@ -310,21 +462,21 @@ Given a method that use a block: end You can convert a proc or lambda to a block argument with the <code>&</code> -operator: +(block conversion) operator: argument = proc { |a| puts "#{a.inspect} was yielded" } my_method(&argument) -If the splat operator comes first in the call, parenthesis must be used to -avoid a warning. +If the block conversion operator comes first in the call, parenthesis must be +used to avoid a warning: -Unlike the splat operator described above the <code>&</code> operator has no -commonly recognized name. + my_method &argument # warning + my_method(&argument) # no warning == Method Lookup -When you send a message Ruby looks up the method that matches the name of the +When you send a message, Ruby looks up the method that matches the name of the message for the receiver. Methods are stored in classes and modules so method lookup walks these, not the objects themselves. @@ -343,7 +495,6 @@ If no match is found this repeats from the beginning, but looking for +method_missing+. The default +method_missing+ is BasicObject#method_missing which raises a NameError when invoked. -If refinements (an experimental feature) are active the method lookup changes. +If refinements (an experimental feature) are active, the method lookup changes. See the {refinements documentation}[rdoc-ref:syntax/refinements.rdoc] for details. - diff --git a/doc/syntax/comments.rdoc b/doc/syntax/comments.rdoc new file mode 100644 index 0000000000..cb6829a984 --- /dev/null +++ b/doc/syntax/comments.rdoc @@ -0,0 +1,253 @@ += Code Comments + +Ruby has two types of comments: inline and block. + +Inline comments start with the <code>#</code> character and continue until the +end of the line: + + # On a separate line + class Foo # or at the end of the line + # can be indented + def bar + end + end + +Block comments start with <code>=begin</code> and end with <code>=end</code>. +Each should start on a separate line. + + =begin + This is + commented out + =end + + class Foo + end + + =begin some_tag + this works, too + =end + +<code>=begin</code> and <code>=end</code> can not be indented, so this is a +syntax error: + + class Foo + =begin + Will not work + =end + end + +== Magic Comments + +While comments are typically ignored by Ruby, special "magic comments" contain +directives that affect how the code is interpreted. + +Top-level magic comments must appear in the first comment section of a file. + +NOTE: Magic comments affect only the file in which they appear; +other files are unaffected. + + # frozen_string_literal: true + + var = 'hello' + var.frozen? # => true + +=== Alternative syntax + +Magic comments may consist of a single directive (as in the example above). +Alternatively, multiple directives may appear on the same line if separated by ";" +and wrapped between "-*-" (see Emacs' {file variables}[https://www.gnu.org/software/emacs/manual/html_node/emacs/Specifying-File-Variables.html]). + + # emacs-compatible; -*- coding: big5; mode: ruby; frozen_string_literal: true -*- + + p 'hello'.frozen? # => true + p 'hello'.encoding # => #<Encoding:Big5> + +=== +encoding+ Directive + +Indicates which string encoding should be used for string literals, +regexp literals and <code>__ENCODING__</code>: + + # encoding: big5 + + ''.encoding # => #<Encoding:Big5> + +Default encoding is UTF-8. + +Top-level magic comments must start on the first line, or on the second line if +the first line looks like <tt>#! shebang line</tt>. + +The word "coding" may be used instead of "encoding". + +=== +frozen_string_literal+ Directive + +Indicates that string literals should be allocated once at parse time and frozen. + + # frozen_string_literal: true + + 3.times do + p 'hello'.object_id # => prints same number + end + p 'world'.frozen? # => true + +The default is false; this can be changed with <code>--enable=frozen-string-literal</code>. +Without the directive, or with <code># frozen_string_literal: false</code>, +the example above would print 3 different numbers and "false". + +Starting in Ruby 3.0, string literals that are dynamic are not frozen nor reused: + + # frozen_string_literal: true + + p "Addition: #{2 + 2}".frozen? # => false + +It must appear in the first comment section of a file. + +=== +warn_indent+ Directive + +This directive can turn on detection of bad indentation for statements that follow it: + + def foo + end # => no warning + + # warn_indent: true + def bar + end # => warning: mismatched indentations at 'end' with 'def' at 6 + +Another way to get these warnings to show is by running Ruby with warnings (<code>ruby -w</code>). Using a directive to set this false will prevent these warnings to show. + +=== +shareable_constant_value+ Directive + +Note: This directive is experimental in Ruby 3.0 and may change in future releases. + +This special directive helps to create constants that hold only immutable objects, or {Ractor-shareable}[rdoc-ref:Ractor@Shareable+and+unshareable+objects] constants. + +The directive can specify special treatment for values assigned to constants: + +* +none+: (default) +* +literal+: literals are implicitly frozen, others must be Ractor-shareable +* +experimental_everything+: all made shareable +* +experimental_copy+: copy deeply and make it shareable + +==== Mode +none+ (default) + +No special treatment in this mode (as in Ruby 2.x): no automatic freezing and no checks. + +It has always been a good idea to deep-freeze constants; Ractor makes this +an even better idea as only the main ractor can access non-shareable constants: + + # shareable_constant_value: none + A = {foo: []} + A.frozen? # => false + Ractor.new { puts A } # => can not access non-shareable objects by non-main Ractor. + +==== Mode +literal+ + +In "literal" mode, constants assigned to literals will be deeply-frozen: + + # shareable_constant_value: literal + X = [{foo: []}] # => same as [{foo: [].freeze}.freeze].freeze + +Other values must be shareable: + + # shareable_constant_value: literal + X = Object.new # => cannot assign unshareable object to X + +Note that only literals directly assigned to constants, or recursively held in such literals will be frozen: + + # shareable_constant_value: literal + var = [{foo: []}] + var.frozen? # => false (assignment was made to local variable) + X = var # => cannot assign unshareable object to X + + X = Set[1, 2, {foo: []}].freeze # => cannot assign unshareable object to X + # (`Set[...]` is not a literal and + # `{foo: []}` is an argument to `Set.[]`) + +The method Module#const_set is not affected. + +==== Mode +experimental_everything+ + +In this mode, all values assigned to constants are made shareable. + + # shareable_constant_value: experimental_everything + FOO = Set[1, 2, {foo: []}] + # same as FOO = Ractor.make_shareable(...) + # OR same as `FOO = Set[1, 2, {foo: [].freeze}.freeze].freeze` + + var = [{foo: []}] + var.frozen? # => false (assignment was made to local variable) + X = var # => calls `Ractor.make_shareable(var)` + var.frozen? # => true + +This mode is "experimental", because it might be error prone, for +example by deep-freezing the constants of an external resource which +could cause errors: + + # shareable_constant_value: experimental_everything + FOO = SomeGem::Something::FOO + # => deep freezes the gem's constant! + +This will be revisited before Ruby 3.1 to either allow `everything` +or to instead remove this mode. + +The method Module#const_set is not affected. + +==== Mode +experimental_copy+ + +In this mode, all values assigned to constants are deeply copied and +made shareable. It is safer mode than +experimental_everything+. + + # shareable_constant_value: experimental_copy + var = [{foo: []}] + var.frozen? # => false (assignment was made to local variable) + X = var # => calls `Ractor.make_shareable(var, copy: true)` + var.frozen? # => false + Ractor.shareable?(X) #=> true + var.object_id == X.object_id #=> false + +This mode is "experimental" and has not been discussed thoroughly. +This will be revisited before Ruby 3.1 to either allow `copy` +or to instead remove this mode. + +The method Module#const_set is not affected. + +==== Scope + +This directive can be used multiple times in the same file: + + # shareable_constant_value: none + A = {foo: []} + A.frozen? # => false + Ractor.new { puts A } # => can not access non-shareable objects by non-main Ractor. + + # shareable_constant_value: literal + B = {foo: []} + B.frozen? # => true + B[:foo].frozen? # => true + + C = [Object.new] # => cannot assign unshareable object to C (Ractor::IsolationError) + + D = [Object.new.freeze] + D.frozen? # => true + + # shareable_constant_value: experimental_everything + E = Set[1, 2, Object.new] + E.frozen? # => true + E.all(&:frozen?) # => true + +The directive affects only subsequent constants and only for the current scope: + + module Mod + # shareable_constant_value: literal + A = [1, 2, 3] + module Sub + B = [4, 5] + end + end + + C = [4, 5] + + module Mod + D = [6] + end + p Mod::A.frozen?, Mod::Sub::B.frozen? # => true, true + p C.frozen?, Mod::D.frozen? # => false, false diff --git a/doc/syntax/control_expressions.rdoc b/doc/syntax/control_expressions.rdoc index 0efc1668ad..3de6cd293f 100644 --- a/doc/syntax/control_expressions.rdoc +++ b/doc/syntax/control_expressions.rdoc @@ -110,7 +110,7 @@ expression as this can be confusing. == +unless+ Expression The +unless+ expression is the opposite of the +if+ expression. If the value -is false the "then" expression is executed: +is false, the "then" expression is executed: unless true puts "the value is a false-value" @@ -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. @@ -204,10 +204,10 @@ Here is an example of using +case+ to compare a String against a pattern: Here the string <code>"12345"</code> is compared with <code>/^1/</code> by calling <code>/^1/ === "12345"</code> which returns +true+. Like the +if+ -expression the first +when+ that matches is executed and all other matches are +expression, the first +when+ that matches is executed and all other matches are ignored. -If no matches are found the +else+ is executed. +If no matches are found, the +else+ is executed. The +else+ and +then+ are optional, this +case+ expression gives the same result as the one above: @@ -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: @@ -300,9 +314,9 @@ This prints the numbers 0 through 11. Like a while loop the condition <code>a > 10</code> is checked when entering the loop and each time the loop body executes. If the condition is false the loop will continue to execute. -Like a +while+ loop the +do+ is optional. +Like a +while+ loop, the +do+ is optional. -Like a +while+ loop the result of an +until+ loop is nil unless +break+ is +Like a +while+ loop, the result of an +until+ loop is nil unless +break+ is used. == +for+ Loop @@ -356,7 +370,7 @@ before the condition: p a # prints 10 -If you don't use +rescue+ or +ensure+ Ruby optimizes away any exception +If you don't use +rescue+ or +ensure+, Ruby optimizes away any exception handling overhead. == +break+ Statement @@ -405,8 +419,8 @@ Use +next+ to skip the rest of the current iteration: p result # prints [2, nil, 6] -+next+ accepts an argument that can be used the result of the current block -iteration: ++next+ accepts an argument that can be used as the result of the current +block iteration: result = [1, 2, 3].map do |value| next value if value.even? @@ -434,16 +448,74 @@ Use +redo+ to redo the current iteration: This prints [0, 1, 3, 3, 5, 5, 7, 7, 9, 9, 11] -In Ruby 1.8 you could also use +retry+ where you used +redo+. This is no +In Ruby 1.8, you could also use +retry+ where you used +redo+. This is no 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 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 = [] @@ -479,11 +551,11 @@ evaluated when the on condition changes: p selected # prints [2] -Here both sides of the flip-flop are evaluated so the flip-flop turns on and +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 = [] @@ -494,7 +566,74 @@ 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 +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 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 diff --git a/doc/syntax/exceptions.rdoc b/doc/syntax/exceptions.rdoc index 0efc35a59f..cdf9d367a7 100644 --- a/doc/syntax/exceptions.rdoc +++ b/doc/syntax/exceptions.rdoc @@ -8,7 +8,7 @@ Exceptions are rescued in a +begin+/+end+ block: # handle exception end -If you are inside a method you do not need to use +begin+ or +end+ unless you +If you are inside a method, you do not need to use +begin+ or +end+ unless you wish to limit the scope of rescued exceptions: def my_method @@ -17,7 +17,14 @@ wish to limit the scope of rescued exceptions: # ... end -The same is true for a +class+ or +module+. +The same is true for a +class+, +module+, and +block+: + + [0, 1, 2].map do |i| + 10 / i + rescue ZeroDivisionError + nil + end + #=> [nil, 10, 5] You can assign the exception to a local variable by using <tt>=> variable_name</tt> at the end of the +rescue+ line: @@ -29,7 +36,7 @@ variable_name</tt> at the end of the +rescue+ line: raise # re-raise the current exception end -By default StandardError and its subclasses are rescued. You can rescue a +By default, StandardError and its subclasses are rescued. You can rescue a specific set of exception classes (and their subclasses) by listing them after +rescue+: @@ -52,7 +59,7 @@ You may rescue different types of exceptions in different ways: end The exception is matched to the rescue section starting at the top, and matches -only once. If an ArgumentError is raised in the begin section it will not be +only once. If an ArgumentError is raised in the begin section, it will not be handled in the StandardError section. You may retry rescued exceptions: @@ -79,7 +86,7 @@ To always run some code whether an exception was raised or not, use +ensure+: rescue # ... ensure - # this always runs + # this always runs BUT does not implicitly return the last evaluated statement. end You may also run some code when an exception is not raised: @@ -89,8 +96,11 @@ You may also run some code when an exception is not raised: rescue # ... else - # this runs only when no exception was raised + # this runs only when no exception was raised AND return the last evaluated statement ensure - # ... + # this always runs. + # It is evaluated after the evaluation of either the `rescue` or the `else` block. + # It will not return implicitly. end +NB : Without explicit +return+ in the +ensure+ block, +begin+/+end+ block will return the last evaluated statement before entering in the +ensure+ block. diff --git a/doc/syntax/keywords.rdoc b/doc/syntax/keywords.rdoc new file mode 100644 index 0000000000..7c368205ef --- /dev/null +++ b/doc/syntax/keywords.rdoc @@ -0,0 +1,162 @@ += Keywords + +The following keywords are used by Ruby. + +__ENCODING__:: + The script encoding of the current file. See Encoding. + +__LINE__:: + The line number of this keyword in the current file. + +__FILE__:: + The path to the current file. + +BEGIN:: + Runs before any other code in the current file. See {miscellaneous + syntax}[rdoc-ref:syntax/miscellaneous.rdoc] + +END:: + Runs after any other code in the current file. See {miscellaneous + syntax}[rdoc-ref:syntax/miscellaneous.rdoc] + +alias:: + Creates an alias between two methods (and other things). See {modules and + classes syntax}[rdoc-ref:syntax/modules_and_classes.rdoc] + +and:: + Short-circuit Boolean and with lower precedence than <code>&&</code> + +begin:: + Starts an exception handling block. See {exceptions + syntax}[rdoc-ref:syntax/exceptions.rdoc] + +break:: + Leaves a block early. See {control expressions + syntax}[rdoc-ref:syntax/control_expressions.rdoc] + +case:: + Starts a +case+ expression. See {control expressions + syntax}[rdoc-ref:syntax/control_expressions.rdoc] + +class:: + Creates or opens a class. See {modules and classes + syntax}[rdoc-ref:syntax/modules_and_classes.rdoc] + +def:: + Defines a method. See {methods syntax}[rdoc-ref:syntax/methods.rdoc] + +defined?:: + Returns a string describing its argument. See {miscellaneous + syntax}[rdoc-ref:syntax/miscellaneous.rdoc] + +do:: + Starts a block. + +else:: + The unhandled condition in +case+, +if+ and +unless+ expressions. See + {control expressions}[rdoc-ref:syntax/control_expressions.rdoc] + +elsif:: + An alternate condition for an +if+ expression. See {control + expressions}[rdoc-ref:syntax/control_expressions.rdoc] + +end:: + The end of a syntax block. Used by classes, modules, methods, exception + handling and control expressions. + +ensure:: + Starts a section of code that is always run when an exception is raised. + See {exception handling}[rdoc-ref:syntax/exceptions.rdoc] + +false:: + Boolean false. See {literals}[rdoc-ref:syntax/literals.rdoc] + +for:: + A loop that is similar to using the +each+ method. See {control + expressions}[rdoc-ref:syntax/control_expressions.rdoc] + +if:: + Used for +if+ and modifier +if+ statements. See {control + expressions}[rdoc-ref:syntax/control_expressions.rdoc] + +in:: + Used to separate the iterable object and iterator variable in a +for+ loop. + See {control expressions}[rdoc-ref:syntax/control_expressions.rdoc] + It also serves as a pattern in a +case+ expression. + See {pattern matching}[rdoc-ref:syntax/pattern_matching.rdoc] + +module:: + Creates or opens a module. See {modules and classes + syntax}[rdoc-ref:syntax/modules_and_classes.rdoc] + +next:: + Skips the rest of the block. See {control + expressions}[rdoc-ref:syntax/control_expressions.rdoc] + +nil:: + A false value usually indicating "no value" or "unknown". See + {literals}[rdoc-ref:syntax/literals.rdoc] + +not:: + Inverts the following boolean expression. Has a lower precedence than + <code>!</code> + +or:: + Boolean or with lower precedence than <code>||</code> + +redo:: + Restarts execution in the current block. See {control + expressions}[rdoc-ref:syntax/control_expressions.rdoc] + +rescue:: + Starts an exception section of code in a +begin+ block. See {exception + handling}[rdoc-ref:syntax/exceptions.rdoc] + +retry:: + Retries an exception block. See {exception + handling}[rdoc-ref:syntax/exceptions.rdoc] + +return:: + Exits a method. See {methods}[rdoc-ref:syntax/methods.rdoc]. + If met in top-level scope, immediately stops interpretation of + the current file. + +self:: + The object the current method is attached to. See + {methods}[rdoc-ref:syntax/methods.rdoc] + +super:: + Calls the current method in a superclass. See + {methods}[rdoc-ref:syntax/methods.rdoc] + +then:: + Indicates the end of conditional blocks in control structures. See + {control expressions}[rdoc-ref:syntax/control_expressions.rdoc] + +true:: + Boolean true. See {literals}[rdoc-ref:syntax/literals.rdoc] + +undef:: + Prevents a class or module from responding to a method call. + See {modules and classes}[rdoc-ref:syntax/modules_and_classes.rdoc] + +unless:: + Used for +unless+ and modifier +unless+ statements. See {control + expressions}[rdoc-ref:syntax/control_expressions.rdoc] + +until:: + Creates a loop that executes until the condition is true. See + {control expressions}[rdoc-ref:syntax/control_expressions.rdoc] + +when:: + A condition in a +case+ expression. See + {control expressions}[rdoc-ref:syntax/control_expressions.rdoc] + +while:: + Creates a loop that executes while the condition is true. See + {control expressions}[rdoc-ref:syntax/control_expressions.rdoc] + +yield:: + Starts execution of the block sent to the current method. See + {methods}[rdoc-ref:syntax/methods.rdoc] + diff --git a/doc/syntax/layout.rdoc b/doc/syntax/layout.rdoc new file mode 100644 index 0000000000..f07447587b --- /dev/null +++ b/doc/syntax/layout.rdoc @@ -0,0 +1,118 @@ += Code Layout + +Expressions in Ruby are separated by line breaks: + + x = 1 + y = 2 + z = x + y + +Line breaks also used as logical separators of the headers of some of control structures from their bodies: + + if z > 3 # line break ends the condition and starts the body + puts "more" + end + + while x < 3 # line break ends the condition and starts the body + x += 1 + end + +<tt>;</tt> can be used as an expressions separator instead of a line break: + + x = 1; y = 2; z = x + y + if z > 3; puts "more"; end + +Traditionally, expressions separated by <tt>;</tt> is used only in short scripts and experiments. + +In some control structures, there is an optional keyword that can be used instead of a line break to separate their elements: + + # if, elsif, until and case ... when: 'then' is an optional separator: + + if z > 3 then puts "more" end + + case x + when Numeric then "number" + when String then "string" + else "object" + end + + # while and until: 'do' is an optional separator + while x < 3 do x +=1 end + +Also, line breaks can be skipped in some places where it doesn't create any ambiguity. Note in the example above: no line break needed before +end+, just as no line break needed after +else+. + +== Breaking expressions in lines + +One expression might be split into several lines when each line can be unambiguously identified as "incomplete" without the next one. + +These works: + + x = # incomplete without something after = + 1 + # incomplete without something after + + 2 + + File.read "test.txt", # incomplete without something after , + enconding: "utf-8" + +These would not: + + # unintended interpretation: + x = 1 # already complete expression + + 2 # interpreted as a separate +2 + + # syntax error: + File.read "test.txt" # already complete expression + , encoding: "utf-8" # attempt to parse as a new expression, SyntaxError + +The exceptions to the rule are lines starting with <tt>.</tt> ("leading dot" style of method calls) or logical operators <tt>&&</tt>/<tt>||</tt> and <tt>and</tt>/<tt>or</tt>: + + # OK, interpreted as a chain of calls + File.read('test.txt') + .strip("\n") + .split("\t") + .sort + + # OK, interpreted as a chain of logical operators: + File.empty?('test.txt') + || File.size('test.txt') < 10 + || File.read('test.txt').strip.empty? + +If the expressions is broken into multiple lines in any of the ways described above, comments between separate lines are allowed: + + sum = base_salary + + # see "yearly bonuses section" + yearly_bonus(year) + + # per-employee coefficient is described + # in another module + personal_coeff(employee) + + # We want to short-circuit on empty files + File.empty?('test.txt') + # Or almost empty ones + || File.size('test.txt') < 10 + # Otherwise we check if it is full of spaces + || File.read('test.txt').strip.empty? + +Finally, the code can explicitly tell Ruby that the expression is continued on the next line with <tt>\\</tt>: + + # Unusual, but works + File.read "test.txt" \ + , encoding: "utf-8" + + # More regular usage (joins the strings on parsing instead + # of concatenating them in runtime, as + would do): + TEXT = "One pretty long line" \ + "one more long line" \ + "one other line of the text" + +The <tt>\\</tt> works as a parse time line break escape, so with it, comments can not be inserted between the lines: + + TEXT = "line 1" \ + # here would be line 2: + "line 2" + + # This is interpreted as if there was no line break where \ is, + # i.e. the same as + TEXT = "line 1" # here would be line 2: + "line 2" + + puts TEXT #=> "line 1" diff --git a/doc/syntax/literals.rdoc b/doc/syntax/literals.rdoc index bfe070b0ab..87a891bf2d 100644 --- a/doc/syntax/literals.rdoc +++ b/doc/syntax/literals.rdoc @@ -2,17 +2,33 @@ Literals create objects you can use in your program. Literals include: -* Booleans and nil -* Numbers -* Strings -* Symbols -* Arrays -* Hashes -* Ranges -* Regular Expressions -* Procs - -== Booleans and nil +* {Boolean and Nil Literals}[#label-Boolean+and+Nil+Literals] +* {Numeric Literals}[#label-Numeric+Literals] + + * {Integer Literals}[#label-Integer+Literals] + * {Float Literals}[#label-Float+Literals] + * {Rational Literals}[#label-Rational+Literals] + * {Complex Literals}[#label-Complex+Literals] + +* {String Literals}[#label-String+Literals] +* {Here Document Literals}[#label-Here+Document+Literals] +* {Symbol Literals}[#label-Symbol+Literals] +* {Array Literals}[#label-Array+Literals] +* {Hash Literals}[#label-Hash+Literals] +* {Range Literals}[#label-Range+Literals] +* {Regexp Literals}[#label-Regexp+Literals] +* {Lambda Proc Literals}[#label-Lambda+Proc+Literals] +* {Percent Literals}[#label-Percent+Literals] + + * {%q: Non-Interpolable String Literals}[#label-25q-3A+Non-Interpolable+String+Literals] + * {% and %Q: Interpolable String Literals}[#label-25+and+-25Q-3A+Interpolable+String+Literals] + * {%w and %W: String-Array Literals}[#label-25w+and+-25W-3A+String-Array+Literals] + * {%i and %I: Symbol-Array Literals}[#label-25i+and+-25I-3A+Symbol-Array+Literals] + * {%r: Regexp Literals}[#label-25r-3A+Regexp+Literals] + * {%s: Symbol Literals}[#label-25s-3A+Symbol+Literals] + * {%x: Backtick Literals}[#label-25x-3A+Backtick+Literals] + +== Boolean and Nil Literals +nil+ and +false+ are both false values. +nil+ is sometimes used to indicate "no value" or "unknown" but evaluates to +false+ in conditional expressions. @@ -20,10 +36,9 @@ Literals create objects you can use in your program. Literals include: +true+ is a true value. All objects except +nil+ and +false+ evaluate to a true value in conditional expressions. -(There are also the constants +TRUE+, +FALSE+ and +NIL+, but the lowercase -literal forms are preferred.) +== \Numeric Literals -== Numbers +=== \Integer Literals You can write integers of any size as follows: @@ -34,15 +49,6 @@ These numbers have the same value, 1,234. The underscore may be used to enhance readability for humans. You may place an underscore anywhere in the number. -Floating point numbers may be written as follows: - - 12.34 - 1234e-2 - 1.234E1 - -These numbers have the same value, 12.34. You may use underscores in floating -point numbers as well. - You can use a special prefix to write numbers in decimal, hexadecimal, octal or binary formats. For decimal numbers use a prefix of <tt>0d</tt>, for hexadecimal numbers use a prefix of <tt>0x</tt>, for octal numbers use a @@ -71,7 +77,68 @@ Examples: All these numbers have the same decimal value, 170. Like integers and floats you may use an underscore for readability. -== Strings +=== \Float Literals + +Floating-point numbers may be written as follows: + + 12.34 + 1234e-2 + 1.234E1 + +These numbers have the same value, 12.34. You may use underscores in floating +point numbers as well. + +=== \Rational Literals + +You can write a Rational literal using a special suffix, <tt>'r'</tt>. + +Examples: + + 1r # => (1/1) + 2/3r # => (2/3) # With denominator. + -1r # => (-1/1) # With signs. + -2/3r # => (-2/3) + 2/-3r # => (-2/3) + -2/-3r # => (2/3) + +1/+3r # => (1/3) + 1.2r # => (6/5) # With fractional part. + 1_1/2_1r # => (11/21) # With embedded underscores. + 2/4r # => (1/2) # Automatically reduced. + +Syntax: + + <rational-literal> = <numerator> [ '/' <denominator> ] 'r' + <numerator> = [ <sign> ] <digits> [ <fractional-part> ] + <fractional-part> = '.' <digits> + <denominator> = [ sign ] <digits> + <sign> = '-' | '+' + <digits> = <digit> { <digit> | '_' <digit> } + <digit> = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' + +Note this, which is parsed as \Float numerator <tt>1.2</tt> +divided by \Rational denominator <tt>3r</tt>, +resulting in a \Float: + + 1.2/3r # => 0.39999999999999997 + +=== \Complex Literals + +You can write a Complex number as follows (suffixed +i+): + + 1i #=> (0+1i) + 1i * 1i #=> (-1+0i) + +Also \Rational numbers may be imaginary numbers. + + 12.3ri #=> (0+(123/10)*i) + ++i+ must be placed after +r+; the opposite is not allowed. + + 12.3ir #=> Syntax error + +== \String Literals + +=== Double-Quoted \String Literals The most common way of writing strings is using <tt>"</tt>: @@ -83,10 +150,14 @@ Any internal <tt>"</tt> must be escaped: "This string has a quote: \". As you can see, it is escaped" -Double-quote strings allow escaped characters such as <tt>\n</tt> for newline, -<tt>\t</tt> for tab, etc. +Double-quoted strings allow escape sequences described in +{Escape Sequences}[#label-Escape+Sequences]. + +In a double-quoted string, +any other character following a backslash is interpreted as the +character itself. -Double-quote strings allow interpolation of other values using +Double-quoted strings allow interpolation of other values using <tt>#{...}</tt>: "One plus one is two: #{1 + 1}" @@ -94,24 +165,107 @@ Double-quote strings allow interpolation of other values using Any expression may be placed inside the interpolated section, but it's best to keep the expression small for readability. -Interpolation may be disabled by escaping the "#" character or using -single-quote strings: +You can also use <tt>#@foo</tt>, <tt>#@@foo</tt> and <tt>#$foo</tt> as a +shorthand for, respectively, <tt>#{ @foo }</tt>, <tt>#{ @@foo }</tt> and +<tt>#{ $foo }</tt>. - '#{1 + 1}' #=> "\#{1 + 1}" +See also: -In addition to disabling interpolation, single-quoted strings also disable all -escape sequences except for the single-quote (<tt>\'</tt>). +* {% and %Q: Interpolable String Literals}[#label-25+and+-25Q-3A+Interpolable+String+Literals] -You may also create strings using <tt>%</tt>: +=== Single-Quoted \String Literals - %(1 + 1 is #{1 + 1}) #=> "1 + 1 is 2" +Interpolation may be disabled by escaping the "#" character or using +single-quoted strings: -There are two different types of <tt>%</tt> strings <tt>%q(...)</tt> behaves -like a single-quote string (no interpolation or character escaping) while -<tt>%Q</tt> behaves as a double-quote string. See Percent Strings below for -more discussion of the syntax of percent strings. + '#{1 + 1}' #=> "\#{1 + 1}" -=== Here Documents +In addition to disabling interpolation, single-quoted strings also disable all +escape sequences except for the single-quote (<tt>\'</tt>) and backslash +(<tt>\\\\</tt>). + +In a single-quoted string, +any other character following a backslash is interpreted as is: +a backslash and the character itself. + +See also: + +* {%q: Non-Interpolable String Literals}[#label-25q-3A+Non-Interpolable+String+Literals] + +=== Literal String Concatenation + +Adjacent string literals are automatically concatenated by the interpreter: + + "con" "cat" "en" "at" "ion" #=> "concatenation" + "This string contains "\ + "no newlines." #=> "This string contains no newlines." + +Any combination of adjacent single-quote, double-quote, percent strings will +be concatenated as long as a percent-string is not last. + + %q{a} 'b' "c" #=> "abc" + "a" 'b' %q{c} #=> NoMethodError: undefined method 'q' for main + +=== Character Literal + +There is also a character literal notation to represent single +character strings, which syntax is a question mark (<tt>?</tt>) +followed by a single character or escape sequence (except continuation line) +that corresponds to a single codepoint in the script encoding: + + ?a #=> "a" + ?abc #=> SyntaxError + ?\n #=> "\n" + ?\s #=> " " + ?\\ #=> "\\" + ?\u{41} #=> "A" + ?\C-a #=> "\x01" + ?\M-a #=> "\xE1" + ?\M-\C-a #=> "\x81" + ?\C-\M-a #=> "\x81", same as above + ?あ #=> "あ" + +=== Escape Sequences + +Some characters can be represented as escape sequences in +double-quoted strings, +character literals, +here document literals (non-quoted, double-quoted, and with backticks), +double-quoted symbols, +double-quoted symbol keys in Hash literals, +Regexp literals, and +several percent literals (<tt>%</tt>, <tt>%Q</tt>, <tt>%W</tt>, <tt>%I</tt>, <tt>%r</tt>, <tt>%x</tt>). + +They allow escape sequences such as <tt>\n</tt> for +newline, <tt>\t</tt> for tab, etc. The full list of supported escape +sequences are as follows: + + \a bell, ASCII 07h (BEL) + \b backspace, ASCII 08h (BS) + \t horizontal tab, ASCII 09h (TAB) + \n newline (line feed), ASCII 0Ah (LF) + \v vertical tab, ASCII 0Bh (VT) + \f form feed, ASCII 0Ch (FF) + \r carriage return, ASCII 0Dh (CR) + \e escape, ASCII 1Bh (ESC) + \s space, ASCII 20h (SPC) + \\ backslash, \ + \nnn octal bit pattern, where nnn is 1-3 octal digits ([0-7]) + \xnn hexadecimal bit pattern, where nn is 1-2 hexadecimal digits ([0-9a-fA-F]) + \unnnn Unicode character, where nnnn is exactly 4 hexadecimal digits ([0-9a-fA-F]) + \u{nnnn ...} Unicode character(s), where each nnnn is 1-6 hexadecimal digits ([0-9a-fA-F]) + \cx or \C-x control character, where x is an ASCII printable character + \M-x meta character, where x is an ASCII printable character + \M-\C-x meta control character, where x is an ASCII printable character + \M-\cx same as above + \c\M-x same as above + \c? or \C-? delete, ASCII 7Fh (DEL) + \<newline> continuation line (empty string) + +The last one, <tt>\<newline></tt>, represents an empty string instead of a character. +It is used to fold a line in a string. + +=== Here Document Literals If you are writing a large block of text you may use a "here document" or "heredoc": @@ -137,13 +291,34 @@ You may indent the ending identifier if you place a "-" after <tt><<</tt>: That might span many lines INDENTED_HEREDOC -Note that the while the closing identifier may be indented, the content is +Note that while the closing identifier may be indented, the content is always treated as if it is flush left. If you indent the content those spaces will appear in the output. -A heredoc allows interpolation and escaped characters. You may disable -interpolation and escaping by surrounding the opening identifier with single -quotes: +To have indented content as well as an indented closing identifier, you can use +a "squiggly" heredoc, which uses a "~" instead of a "-" after <tt><<</tt>: + + expected_result = <<~SQUIGGLY_HEREDOC + This would contain specially formatted text. + + That might span many lines + SQUIGGLY_HEREDOC + +The indentation of the least-indented line will be removed from each line of +the content. Note that empty lines and lines consisting solely of literal tabs +and spaces will be ignored for the purposes of determining indentation, but +escaped tabs and spaces are considered non-indentation characters. + +For the purpose of measuring an indentation, a horizontal tab is regarded as a +sequence of one to eight spaces such that the column position corresponding to +its end is a multiple of eight. The amount to be removed is counted in terms +of the number of spaces. If the boundary appears in the middle of a tab, that +tab is not removed. + +A heredoc allows interpolation and the escape sequences described in +{Escape Sequences}[#label-Escape+Sequences]. +You may disable interpolation and the escaping by surrounding the opening +identifier with single quotes: expected_result = <<-'EXPECTED' One plus one is #{1 + 1} @@ -159,6 +334,9 @@ behaves like Kernel#`: cat #{__FILE__} HEREDOC +When surrounding with quotes, any character but that quote and newline +(CR and/or LF) can be used as the identifier. + To call a method on a heredoc place it after the opening identifier: expected_result = <<-EXPECTED.chomp @@ -174,29 +352,34 @@ read: content for heredoc two TWO -== Symbols +== \Symbol Literals A Symbol represents a name inside the ruby interpreter. See Symbol for more details on what symbols are and when ruby creates them internally. You may reference a symbol using a colon: <tt>:my_symbol</tt>. -You may also create symbols by interpolation: +You may also create symbols by interpolation and escape sequences described in +{Escape Sequences}[#label-Escape+Sequences] with double-quotes: :"my_symbol1" :"my_symbol#{1 + 1}" + :"foo\sbar" -Note that symbols are never garbage collected so be careful when referencing -symbols using interpolation. +Like strings, a single-quote may be used to disable interpolation and +escape sequences: -Like strings, a single-quote may be used to disable interpolation: + :'my_symbol#{1 + 1}' #=> :"my_symbol\#{1 + 1}" - :"my_symbol#{1 + 1}" #=> :"my_symbol\#{1 + 1}" - -When creating a Hash there is a special syntax for referencing a Symbol as +When creating a Hash, there is a special syntax for referencing a Symbol as well. -== Arrays +See also: + +* {%s: Symbol Literals}[#label-25s-3A+Symbol+Literals] + + +== \Array Literals An array is created using the objects between <tt>[</tt> and <tt>]</tt>: @@ -207,9 +390,14 @@ You may place expressions inside the array: [1, 1 + 1, 1 + 2] [1, [1 + 1, [1 + 2]]] +See also: + +* {%w and %W: String-Array Literals}[#label-25w+and+-25W-3A+String-Array+Literals] +* {%i and %I: Symbol-Array Literals}[#label-25i+and+-25I-3A+Symbol-Array+Literals] + See Array for the methods you may use with an array. -== Hashes +== \Hash Literals A hash is created using key-value pairs between <tt>{</tt> and <tt>}</tt>: @@ -223,40 +411,62 @@ You can create a hash using symbol keys with the following syntax: This same syntax is used for keyword arguments for a method. +Like Symbol literals, you can quote symbol keys. + + { "a 1": 1, "b #{1 + 1}": 2 } + +is equal to + + { :"a 1" => 1, :"b 2" => 2 } + +Hash values can be omitted, meaning that value will be fetched from the context +by the name of the key: + + x = 100 + y = 200 + h = { x:, y: } + #=> {:x=>100, :y=>200} + See Hash for the methods you may use with a hash. -== Ranges +== \Range Literals A range represents an interval of values. The range may include or exclude its ending value. (1..2) # includes its ending value (1...2) # excludes its ending value + (1..) # endless range, representing infinite sequence from 1 to Infinity + (..1) # beginless range, representing infinite sequence from -Infinity to 1 You may create a range of any object. See the Range documentation for details on the methods you need to implement. -== Regular Expressions +== \Regexp Literals -A regular expression is created using "/": +A regular expression may be created using leading and trailing +slash (<tt>'/'</tt>) characters: - /my regular expression/ + re = /foo/ # => /foo/ + re.class # => Regexp -The regular expression may be followed by flags which adjust the matching -behavior of the regular expression. The "i" flag makes the regular expression -case-insensitive: - - /my regular expression/i +The trailing slash may be followed by one or more modifiers characters +that set modes for the regexp. +See {Regexp modes}[rdoc-ref:Regexp@Modes] for details. Interpolation may be used inside regular expressions along with escaped characters. Note that a regular expression may require additional escaped characters than a string. +See also: + +* {%r: Regexp Literals}[#label-25r-3A+Regexp+Literals] + See Regexp for a description of the syntax of regular expressions. -== Procs +== Lambda Proc Literals -A proc can be created with <tt>-></tt>: +A lambda proc can be created with <tt>-></tt>: -> { 1 + 1 } @@ -268,28 +478,148 @@ You can require arguments for the proc as follows: This proc will add one to its argument. -== Percent Strings +== Percent Literals + +Each of the literals in described in this section +may use these paired delimiters: + +* <tt>[</tt> and <tt>]</tt>. +* <tt>(</tt> and <tt>)</tt>. +* <tt>{</tt> and <tt>}</tt>. +* <tt><</tt> and <tt>></tt>. +* Non-alphanumeric ASCII character except above, as both beginning and ending delimiters. + +The delimiters can be escaped with a backslash. +However, the first four pairs (brackets, parenthesis, braces, and +angle brackets) are allowed without backslash as far as they are correctly +paired. + +These are demonstrated in the next section. + +=== <tt>%q</tt>: Non-Interpolable String Literals + +You can write a non-interpolable string with <tt>%q</tt>. +The created string is the same as if you created it with single quotes: + + %q[foo bar baz] # => "foo bar baz" # Using []. + %q(foo bar baz) # => "foo bar baz" # Using (). + %q{foo bar baz} # => "foo bar baz" # Using {}. + %q<foo bar baz> # => "foo bar baz" # Using <>. + %q|foo bar baz| # => "foo bar baz" # Using two |. + %q:foo bar baz: # => "foo bar baz" # Using two :. + %q(1 + 1 is #{1 + 1}) # => "1 + 1 is \#{1 + 1}" # No interpolation. + %q[foo[bar]baz] # => "foo[bar]baz" # brackets can be nested. + %q(foo(bar)baz) # => "foo(bar)baz" # parenthesis can be nested. + %q{foo{bar}baz} # => "foo{bar}baz" # braces can be nested. + %q<foo<bar>baz> # => "foo<bar>baz" # angle brackets can be nested. + +This is similar to single-quoted string but only backslashes and +the specified delimiters can be escaped with a backslash. + +=== <tt>% and %Q</tt>: Interpolable String Literals + +You can write an interpolable string with <tt>%Q</tt> +or with its alias <tt>%</tt>: + + %[foo bar baz] # => "foo bar baz" + %(1 + 1 is #{1 + 1}) # => "1 + 1 is 2" # Interpolation. + +This is similar to double-quoted string. +It allow escape sequences described in +{Escape Sequences}[#label-Escape+Sequences]. +Other escaped characters (a backslash followed by a character) are +interpreted as the character. + +=== <tt>%w and %W</tt>: String-Array Literals + +You can write an array of strings as whitespace-separated words +with <tt>%w</tt> (non-interpolable) or <tt>%W</tt> (interpolable): + + %w[foo bar baz] # => ["foo", "bar", "baz"] + %w[1 % *] # => ["1", "%", "*"] + # Use backslash to embed spaces in the strings. + %w[foo\ bar baz\ bat] # => ["foo bar", "baz bat"] + %W[foo\ bar baz\ bat] # => ["foo bar", "baz bat"] + %w(#{1 + 1}) # => ["\#{1", "+", "1}"] + %W(#{1 + 1}) # => ["2"] + + # The nested delimiters evaluated to a flat array of strings + # (not nested array). + %w[foo[bar baz]qux] # => ["foo[bar", "baz]qux"] + +The following characters are considered as white spaces to separate words: + +* space, ASCII 20h (SPC) +* form feed, ASCII 0Ch (FF) +* newline (line feed), ASCII 0Ah (LF) +* carriage return, ASCII 0Dh (CR) +* horizontal tab, ASCII 09h (TAB) +* vertical tab, ASCII 0Bh (VT) + +The white space characters can be escaped with a backslash to make them +part of a word. + +<tt>%W</tt> allow escape sequences described in +{Escape Sequences}[#label-Escape+Sequences]. +However the continuation line <tt>\<newline></tt> is not usable because +it is interpreted as the escaped newline described above. + +=== <tt>%i and %I</tt>: Symbol-Array Literals + +You can write an array of symbols as whitespace-separated words +with <tt>%i</tt> (non-interpolable) or <tt>%I</tt> (interpolable): + + %i[foo bar baz] # => [:foo, :bar, :baz] + %i[1 % *] # => [:"1", :%, :*] + # Use backslash to embed spaces in the symbols. + %i[foo\ bar baz\ bat] # => [:"foo bar", :"baz bat"] + %I[foo\ bar baz\ bat] # => [:"foo bar", :"baz bat"] + %i(#{1 + 1}) # => [:"\#{1", :+, :"1}"] + %I(#{1 + 1}) # => [:"2"] + +The white space characters and its escapes are interpreted as the same as +string-array literals described in +{%w and %W: String-Array Literals}[#label-25w+and+-25W-3A+String-Array+Literals]. + +=== <tt>%s</tt>: Symbol Literals + +You can write a symbol with <tt>%s</tt>: + + %s[foo] # => :foo + %s[foo bar] # => :"foo bar" + +This is non-interpolable. +No interpolation allowed. +Only backslashes and the specified delimiters can be escaped with a backslash. + +=== <tt>%r</tt>: Regexp Literals + +You can write a regular expression with <tt>%r</tt>; +the character used as the leading and trailing delimiter +may be (almost) any character: + + %r/foo/ # => /foo/ + %r:name/value pair: # => /name\/value pair/ -Besides <tt>%(...)</tt> which creates a String, The <tt>%</tt> may create -other types of object. As with strings, an uppercase letter allows -interpolation and escaped characters while a lowercase letter disables them. +A few "symmetrical" character pairs may be used as delimiters: -These are the types of percent strings in ruby: + %r[foo] # => /foo/ + %r{foo} # => /foo/ + %r(foo) # => /foo/ + %r<foo> # => /foo/ -<tt>%i</tt> :: Array of Symbols -<tt>%q</tt> :: String -<tt>%r</tt> :: Regular Expression -<tt>%s</tt> :: Symbol -<tt>%w</tt> :: Array of Strings -<tt>%x</tt> :: Backtick (capture subshell result) +The trailing delimiter may be followed by one or more modifier characters +that set modes for the regexp. +See {Regexp modes}[rdoc-ref:Regexp@Modes] for details. -For the two array forms of percent string, if you wish to include a space in -one of the array entries you must escape it with a "\\" character: +=== <tt>%x</tt>: Backtick Literals - %w[one one-hundred\ one] - #=> ["one", "one-hundred one"] +You can write and execute a shell command with <tt>%x</tt>: -If you are using "(", "[", "{", "<" you must close it with ")", "]", "}", ">" -respectively. You may use most other non-alphanumeric characters for percent -string delimiters such as "%", "|", "^", etc. + %x(echo 1) # => "1\n" + %x[echo #{1 + 2}] # => "3\n" + %x[echo \u0030] # => "0\n" +This is interpolable. +<tt>%x</tt> allow escape sequences described in +{Escape Sequences}[#label-Escape+Sequences]. diff --git a/doc/syntax/methods.rdoc b/doc/syntax/methods.rdoc index 7fd69983f3..14810a188f 100644 --- a/doc/syntax/methods.rdoc +++ b/doc/syntax/methods.rdoc @@ -8,8 +8,13 @@ definition: end A method definition consists of the +def+ keyword, a method name, the body of -the method, then the +end+ keyword. When called the method will execute the -body of the method. This method returns +2+. +the method, +return+ value and the +end+ keyword. When called the method will +execute the body of the method. This method returns +2+. + +Since Ruby 3.0, there is also a shorthand syntax for methods consisting +of exactly one expression: + + def one_plus_one = 1 + 1 This section only covers defining methods. See also the {syntax documentation on calling methods}[rdoc-ref:syntax/calling_methods.rdoc]. @@ -17,34 +22,72 @@ on calling methods}[rdoc-ref:syntax/calling_methods.rdoc]. == Method Names Method names may be one of the operators or must start a letter or a character -with the eight bit set. Typically method names are US-ASCII compatible since -the keys to type them exist on all keyboards. +with the eighth bit set. It may contain letters, numbers, an <code>_</code> +(underscore or low line) or a character with the eighth bit set. The convention +is to use underscores to separate words in a multiword method name: + + def method_name + puts "use underscores to separate words" + end + +Ruby programs must be written in a US-ASCII-compatible character set such as +UTF-8, ISO-8859-1 etc. In such character sets if the eighth bit is set it +indicates an extended character. Ruby allows method names and other identifiers +to contain such characters. Ruby programs cannot contain some characters like +ASCII NUL (<code>\x00</code>). + +The following are examples of valid Ruby methods: + + def hello + "hello" + end -(Ruby programs must be written in a US-ASCII-compatible character set. In -such character sets if the eight bit is set it indicates an extended -character. Ruby allows method names and other identifiers to contain such -characters.) + def こんにちは + puts "means hello in Japanese" + end -Method names may contain letters, numbers, an <code>_</code> (underscore or -low line) or a character with the eight bit set. +Typically method names are US-ASCII compatible since the keys to type them +exist on all keyboards. Method names may end with a <code>!</code> (bang or exclamation mark), a -<code>?</code> (question mark) or <code>=</code> equals sign. +<code>?</code> (question mark), or <code>=</code> (equals sign). -In the ruby core library when a method ends with a bang it indicates there is -a non-bang method that has does not modify the receiver. This is typically -true for the standard library but does not hold true for other ruby libraries. +The bang methods (<code>!</code> at the end of the method name) are called and +executed just like any other method. However, by convention, a method with an +exclamation point or bang is considered dangerous. In Ruby's core library the +dangerous method implies that when a method ends with a bang (<code>!</code>), +it indicates that unlike its non-bang equivalent, permanently modifies its +receiver. Almost always, the Ruby core library will have a non-bang +counterpart (method name which does NOT end with <code>!</code>) of every bang +method (method name which does end with <code>!</code>) that does not modify +the receiver. This convention is typically true for the Ruby core library but +may or may not hold true for other Ruby libraries. -Methods that end with a question mark do not always return just +true+ or -+false+. Often they will may return an object to indicate a true value (or -"truthy" value). +Methods that end with a question mark by convention return boolean, but they +may not always return just +true+ or +false+. Often, they will return an +object to indicate a true value (or "truthy" value). -Methods that end with an equals sign indicate an assignment method. For -assignment methods the return value is ignored, the arguments are returned -instead. +Methods that end with an equals sign indicate an assignment method. -These are method names for the various ruby operators. Each of these -operators accept only one argument. Following the operator is the typical + class C + def attr + @attr + end + + def attr=(val) + @attr = val + end + end + + c = C.new + c.attr #=> nil + c.attr = 10 # calls "attr=(10)" + c.attr #=> 10 + +Assignment methods can not be defined using the shorthand syntax. + +These are method names for the various Ruby operators. Each of these +operators accepts only one argument. Following the operator is the typical use or name of the operator. Creating an alternate meaning for the operator may lead to confusion as the user expects plus to add things, minus to subtract things, etc. Additionally, you cannot alter the precedence of the @@ -57,6 +100,7 @@ operators. <code>/</code> :: divide <code>%</code> :: modulus division, String#% <code>&</code> :: AND +<code>|</code> :: OR <code>^</code> :: XOR (exclusive OR) <code>>></code> :: right-shift <code><<</code> :: left-shift, append @@ -71,8 +115,8 @@ operators. <code>></code> :: greater-than <code>>=</code> :: greater-than or equal -To define unary methods minus, plus, tilde and not (<code>!</code>) follow the -operator with an <code>@</code> as in <code>+@</code> or <code>!@</code>: +To define unary methods minus and plus, follow the operator with an +<code>@</code> as in <code>+@</code>: class C def -@ @@ -84,8 +128,34 @@ operator with an <code>@</code> as in <code>+@</code> or <code>!@</code>: -obj # prints "you inverted this object" +The <code>@</code> is needed to differentiate unary minus and plus +operators from binary minus and plus operators. + +You can also follow tilde and not (<code>!</code>) unary methods with +<code>@</code>, but it is not required as there are no binary tilde +and not operators. + Unary methods accept zero arguments. +Additionally, methods for element reference and assignment may be defined: +<code>[]</code> and <code>[]=</code> respectively. Both can take one or more +arguments, and element reference can take none. + + class C + def [](a, b) + puts a + b + end + + def []=(a, b, c) + puts a * b + c + end + end + + obj = C.new + + obj[2, 3] # prints "5" + obj[2, 3] = 4 # prints "10" + == Return Values By default, a method returns the last expression that was evaluated in the body @@ -105,14 +175,18 @@ evaluated. 1 + 1 # this expression is never evaluated end -Note that for assignment methods the return value will always be ignored. -Instead the argument will be returned: +Note that for assignment methods the return value will be ignored when using +the assignment syntax. Instead, the argument will be returned: def a=(value) return 1 + value end - p(a = 5) # prints 5 + p(self.a = 5) # prints 5 + +The actual return value will be returned when invoking the method directly: + + p send(:a=, 5) # prints 6 == Scope @@ -207,6 +281,13 @@ The parentheses around the arguments are optional: value + 1 end +The parentheses are mandatory in shorthand method definitions: + + # OK + def add_one(value) = value + 1 + # SyntaxError + def add_one value = value + 1 + Multiple arguments are separated by a comma: def add_values(a, b) @@ -237,6 +318,25 @@ This will raise a SyntaxError: a + b + c end +Default argument values can refer to arguments that have already been +evaluated as local variables, and argument values are always evaluated +left to right. So this is allowed: + + def add_values(a = 1, b = a) + a + b + end + add_values + # => 2 + +But this will raise a +NameError+ (unless there is a method named ++b+ defined): + + def add_values(a = b, b = 1) + a + b + end + add_values + # NameError (undefined local variable or method `b' for main:Object) + === Array Decomposition You can decompose (unpack or extract values from) an Array using extra @@ -309,11 +409,22 @@ converted to an Array: gather_arguments 1, 2, 3 # prints [1, 2, 3] -The array argument must be the last positional argument, it must appear before -any keyword arguments. +The array argument must appear before any keyword arguments. + +It is possible to gather arguments at the beginning or in the middle: + + def gather_arguments(first_arg, *middle_arguments, last_arg) + p middle_arguments + end -The array argument will capture a Hash as the last entry if a hash was sent by -the caller after all positional arguments. + gather_arguments 1, 2, 3, 4 # prints [2, 3] + +The array argument will capture a Hash as the last entry if keywords were +provided by the caller after all positional arguments. + + def gather_arguments(*arguments) + p arguments + end gather_arguments 1, a: 2 # prints [1, {:a=>2}] @@ -331,6 +442,13 @@ Also, note that a bare <code>*</code> can be used to ignore arguments: def ignore_arguments(*) end +You can also use a bare <code>*</code> when calling a method to pass the +arguments directly to another method: + + def delegate_arguments(*) + other_method(*) + end + === Keyword Arguments Keyword arguments are similar to positional arguments with default values: @@ -349,18 +467,61 @@ Arbitrary keyword arguments will be accepted with <code>**</code>: # prints 1 then {:second=>2, :third=>3} When calling a method with keyword arguments the arguments may appear in any -order. If an unknown keyword argument is sent by the caller an ArgumentError -is raised. +order. If an unknown keyword argument is sent by the caller, and the method +does not accept arbitrary keyword arguments, an ArgumentError is raised. + +To require a specific keyword argument, do not include a default value +for the keyword argument: + + def add_values(first:, second:) + first + second + end + add_values + # ArgumentError (missing keywords: first, second) + add_values(first: 1, second: 2) + # => 3 When mixing keyword arguments and positional arguments, all positional arguments must appear before any keyword arguments. -== Block Argument +Also, note that <code>**</code> can be used to ignore keyword arguments: + + def ignore_keywords(**) + end + +You can also use <code>**</code> when calling a method to delegate +keyword arguments to another method: + + def delegate_keywords(**) + other_method(**) + end + +To mark a method as accepting keywords, but not actually accepting +keywords, you can use the <code>**nil</code>: + + def no_keywords(**nil) + end + +Calling such a method with keywords or a non-empty keyword splat will +result in an ArgumentError. This syntax is supported so that keywords +can be added to the method later without affected backwards compatibility. + +If a method definition does not accept any keywords, and the +<code>**nil</code> syntax is not used, any keywords provided when calling +the method will be converted to a Hash positional argument: + + def meth(arg) + arg + end + meth(a: 1) + # => {:a=>1} + +=== Block Argument The block argument is indicated by <code>&</code> and must come last: def my_method(&my_block) - my_method.call(self) + my_block.call(self) end Most frequently the block argument is used to pass a block to another method: @@ -369,8 +530,15 @@ Most frequently the block argument is used to pass a block to another method: @items.each(&block) end +You are not required to give a name to the block if you will just be passing +it to another method: + + def each_item(&) + @items.each(&) + end + If you are only going to call the block and will not otherwise manipulate it -or send it to another method using <code>yield</code> without an explicit +or send it to another method, using <code>yield</code> without an explicit block parameter is preferred. This method is equivalent to the first method in this section: @@ -378,14 +546,64 @@ in this section: yield self end -There is also a performance benefit to using yield over a calling a block -parameter. When a block argument is assigned to a variable a Proc object is -created which holds the block. When using yield this Proc object is not -created. +=== Argument Forwarding + +Since Ruby 2.7, an all-arguments forwarding syntax is available: -If you only need to use the block sometimes you can use Proc.new to create a -proc from the block that was passed to your method. See Proc.new for further -details. + def concrete_method(*positional_args, **keyword_args, &block) + [positional_args, keyword_args, block] + end + + def forwarding_method(...) + concrete_method(...) + end + + forwarding_method(1, b: 2) { puts 3 } + #=> [[1], {:b=>2}, #<Proc:...skip...>] + +Calling with forwarding <code>...</code> is available only in methods +defined with <code>...</code>. + + def regular_method(arg, **kwarg) + concrete_method(...) # Syntax error + end + +Since Ruby 3.0, there can be leading arguments before <code>...</code> +both in definitions and in invocations (but in definitions they can be +only positional arguments without default values). + + def request(method, path, **headers) + puts "#{method.upcase} #{path} #{headers}" + end + + def get(...) + request(:GET, ...) # leading argument in invoking + end + + get('http://ruby-lang.org', 'Accept' => 'text/html') + # Prints: GET http://ruby-lang.org {"Accept"=>"text/html"} + + def logged_get(msg, ...) # leading argument in definition + puts "Invoking #get: #{msg}" + get(...) + end + + logged_get('Ruby site', 'http://ruby-lang.org') + # Prints: + # Invoking #get: Ruby site + # GET http://ruby-lang.org {} + +Note that omitting parentheses in forwarding calls may lead to +unexpected results: + + def log(...) + puts ... # This would be treated as `puts()...', + # i.e. endless range from puts result + end + + log("test") + # Prints: warning: ... at EOL, should be parenthesized? + # ...and then empty line == Exception Handling @@ -408,7 +626,28 @@ May be written as: # handle exception end -If you wish to rescue an exception for only part of your method use +begin+ and +Similarly, if you wish to always run code even if an exception is raised, +you can use +ensure+ without +begin+ and +end+: + + def my_method + # code that may raise an exception + ensure + # code that runs even if previous code raised an exception + end + +You can also combine +rescue+ with +ensure+ and/or +else+, without ++begin+ and +end+: + + def my_method + # code that may raise an exception + rescue + # handle exception + else + # only run if no exception raised above + ensure + # code that runs even if previous code raised an exception + end + +If you wish to rescue an exception for only part of your method, use +begin+ and +end+. For more details see the page on {exception handling}[rdoc-ref:syntax/exceptions.rdoc]. - diff --git a/doc/syntax/miscellaneous.rdoc b/doc/syntax/miscellaneous.rdoc index 8f424f019f..d5cfd3e474 100644 --- a/doc/syntax/miscellaneous.rdoc +++ b/doc/syntax/miscellaneous.rdoc @@ -10,16 +10,16 @@ most frequently used with <code>ruby -e</code>. == Indentation -Ruby does not require any indentation. Typically ruby programs are indented +Ruby does not require any indentation. Typically, ruby programs are indented two spaces. -If you run ruby with warnings enabled and have an indentation mis-match you +If you run ruby with warnings enabled and have an indentation mismatch, you will receive a warning. == +alias+ The +alias+ keyword is most frequently used to alias methods. When aliasing a -method you can use either its name or a symbol: +method, you can use either its name or a symbol: alias new_name old_name alias :new_name :old_name @@ -61,7 +61,7 @@ You may use +undef+ in any scope. See also Module#undef_method p defined?(RUBY_VERSION) # prints "constant" p defined?(1 + 1) # prints "method" -You don't need to use parenthesis with +defined?+ but they are recommended due +You don't need to use parenthesis with +defined?+, but they are recommended due to the {low precedence}[rdoc-ref:syntax/precedence.rdoc] of +defined?+. For example, if you wish to check if an instance variable exists and that the @@ -69,7 +69,7 @@ instance variable is zero: defined? @instance_variable && @instance_variable.zero? -This returns <code>"expression"</code> which is not what you want if the +This returns <code>"expression"</code>, which is not what you want if the instance variable is not defined. @instance_variable = 1 @@ -83,6 +83,36 @@ Using the specific reflection methods such as instance_variable_defined? for instance variables or const_defined? for constants is less error prone than using +defined?+. ++defined?+ handles some regexp global variables specially based on whether +there is an active regexp match and how many capture groups there are: + + /b/ =~ 'a' + defined?($~) # => "global-variable" + defined?($&) # => nil + defined?($`) # => nil + defined?($') # => nil + defined?($+) # => nil + defined?($1) # => nil + defined?($2) # => nil + + /./ =~ 'a' + defined?($~) # => "global-variable" + defined?($&) # => "global-variable" + defined?($`) # => "global-variable" + defined?($') # => "global-variable" + defined?($+) # => nil + defined?($1) # => nil + defined?($2) # => nil + + /(.)/ =~ 'a' + defined?($~) # => "global-variable" + defined?($&) # => "global-variable" + defined?($`) # => "global-variable" + defined?($') # => "global-variable" + defined?($+) # => "global-variable" + defined?($1) # => "global-variable" + defined?($2) # => nil + == +BEGIN+ and +END+ +BEGIN+ defines a block that is run before any other code in the current file. @@ -104,4 +134,3 @@ Here is an example one-liner that adds numbers from standard input or any files in the argument list: ruby -ne 'BEGIN { count = 0 }; END { puts count }; count += gets.to_i' - diff --git a/doc/syntax/modules_and_classes.rdoc b/doc/syntax/modules_and_classes.rdoc index f4ab1ea6f9..9e05c5c774 100644 --- a/doc/syntax/modules_and_classes.rdoc +++ b/doc/syntax/modules_and_classes.rdoc @@ -4,8 +4,8 @@ Modules serve two purposes in Ruby, namespacing and mix-in functionality. A namespace can be used to organize code by package or functionality that separates common names from interference by other packages. For example, the -Curses namespace provides functionality for curses that prevents a collision -for the common name "Window". +IRB namespace provides functionality for irb that prevents a collision +for the common name "Context". Mix-in functionality allows sharing common methods across multiple classes or modules. Ruby comes with the Enumerable mix-in module which provides many @@ -40,9 +40,9 @@ functionality: remove_method :my_method end -Reopening classes is a very powerful feature of Ruby, but it is best to only -reopen classes you own. Reopening classes you do not own may lead to naming -conflicts or difficult to diagnose bugs. +Reopening modules (or classes) is a very powerful feature of Ruby, but it is +best to only reopen modules you own. Reopening modules you do not own may lead +to naming conflicts or difficult to diagnose bugs. == Nesting @@ -94,7 +94,7 @@ nesting: end However, if you use <code>::</code> to define <code>A::B</code> without -nesting it inside +A+ a NameError exception will be raised because the nesting +nesting it inside +A+, a NameError exception will be raised because the nesting does not include +A+: module A @@ -129,7 +129,7 @@ method on a module is often called a "class method" instead of a "module method". See also Module#module_function which can convert an instance method into a class method.) -When a class method references a constant it uses the same rules as referencing +When a class method references a constant, it uses the same rules as referencing it outside the method as the scope is the same. Instance methods defined in a module are only callable when included. These @@ -155,8 +155,8 @@ Ruby has three types of visibility. The default is +public+. A public method may be called from any other object. The second visibility is +protected+. When calling a protected method the -sender must be a subclass of the receiver or the receiver must be a subclass of -the sender. Otherwise a NoMethodError will be raised. +sender must inherit the Class or Module which defines the method. Otherwise a +NoMethodError will be raised. Protected visibility is most frequently used to define <code>==</code> and other comparison methods where the author does not wish to expose an object's @@ -190,9 +190,41 @@ Here is an example: b.n b #=> 1 -- m called on defining class a.n b # raises NoMethodError A is not a subclass of B -The third visibility is +private+. A private method may not be called with a -receiver, not even +self+. If a private method is called with a receiver a -NoMethodError will be raised. +The third visibility is +private+. A private method may only be called from +inside the owner class without a receiver, or with a literal +self+ +as a receiver. If a private method is called with a +receiver other than a literal +self+, a NoMethodError will be raised. + + class A + def without + m + end + + def with_self + self.m + end + + def with_other + A.new.m + end + + def with_renamed + copy = self + copy.m + end + + def m + 1 + end + + private :m + end + + a = A.new + a.without #=> 1 + a.with_self #=> 1 + a.with_other # NoMethodError (private method `m' called for #<A:0x0000559c287f27d0>) + a.with_renamed # NoMethodError (private method `m' called for #<A:0x0000559c285f8330>) === +alias+ and +undef+ @@ -227,6 +259,28 @@ includes a minimum of built-in methods. You can use BasicObject to create an independent inheritance structure. See the BasicObject documentation for further details. +Just like modules, classes can also be reopened. You can omit its superclass +when you reopen a class. Specifying a different superclass than the previous +definition will raise an error. + + class C + end + + class D < C + end + + # OK + class D < C + end + + # OK + class D + end + + # TypeError: superclass mismatch for class D + class D < String + end + == Inheritance Any method defined on a class is callable from its subclass: @@ -342,4 +396,3 @@ is equivalent to this code block: end Both objects will have a +my_method+ that returns +2+. - diff --git a/doc/syntax/operators.rdoc b/doc/syntax/operators.rdoc new file mode 100644 index 0000000000..d3045ac99e --- /dev/null +++ b/doc/syntax/operators.rdoc @@ -0,0 +1,75 @@ += Operators + +In Ruby, operators such as <code>+</code>, are defined as methods on the class. +Literals[rdoc-ref:syntax/literals.rdoc] define their methods within the lower +level, C language. String class, for example. + +Ruby objects can define or overload their own implementation for most operators. + +Here is an example: + + class Foo < String + def +(str) + self.concat(str).concat("another string") + end + end + + foobar = Foo.new("test ") + puts foobar + "baz " + +This prints: + + test baz another string + +What operators are available is dependent on the implementing class. + +== Operator Behavior + +How a class behaves to a given operator is specific to that class, since +operators are method implementations. + +When using an operator, it's the expression on the left-hand side of the +operation that specifies the behavior. + + 'a' * 3 #=> "aaa" + 3 * 'a' # TypeError: String can't be coerced into Integer + +== Logical Operators + +Logical operators are not methods, and therefore cannot be +redefined/overloaded. They are tokenized at a lower level. + +Short-circuit logical operators (<code>&&</code>, <code>||</code>, +<code>and</code>, and <code>or</code>) do not always result in a boolean value. +Similar to blocks, it's the last evaluated expression that defines the result +of the operation. + +=== <code>&&</code>, <code>and</code> + +Both <code>&&</code>/<code>and</code> operators provide short-circuiting by executing each +side of the operator, left to right, and stopping at the first occurrence of a +falsey expression. The expression that defines the result is the last one +executed, whether it be the final expression, or the first occurrence of a falsey +expression. + +Some examples: + + true && 9 && "string" #=> "string" + (1 + 2) && nil && "string" #=> nil + (a = 1) && (b = false) && (c = "string") #=> false + + puts a #=> 1 + puts b #=> false + puts c #=> nil + +In this last example, <code>c</code> was initialized, but not defined. + +=== <code>||</code>, <code>or</code> + +The means by which <code>||</code>/<code>or</code> short-circuits, is to return the result of +the first expression that is truthy. + +Some examples: + + (1 + 2) || true || "string" #=> 3 + false || nil || "string" #=> "string" diff --git a/doc/syntax/pattern_matching.rdoc b/doc/syntax/pattern_matching.rdoc new file mode 100644 index 0000000000..06aae26d49 --- /dev/null +++ b/doc/syntax/pattern_matching.rdoc @@ -0,0 +1,528 @@ += Pattern matching + +Pattern matching is a feature allowing deep matching of structured values: checking the structure and binding the matched parts to local variables. + +Pattern matching in Ruby is implemented with the +case+/+in+ expression: + + case <expression> + in <pattern1> + ... + in <pattern2> + ... + in <pattern3> + ... + else + ... + end + +(Note that +in+ and +when+ branches can NOT be mixed in one +case+ expression.) + +Or with the <code>=></code> operator and the +in+ operator, which can be used in a standalone expression: + + <expression> => <pattern> + + <expression> in <pattern> + +The +case+/+in+ expression is _exhaustive_: if the value of the expression does not match any branch of the +case+ expression (and the +else+ branch is absent), +NoMatchingPatternError+ is raised. + +Therefore, the +case+ expression might be used for conditional matching and unpacking: + + config = {db: {user: 'admin', password: 'abc123'}} + + case config + in db: {user:} # matches subhash and puts matched value in variable user + puts "Connect with user '#{user}'" + in connection: {username: } + puts "Connect with user '#{username}'" + else + puts "Unrecognized structure of config" + end + # Prints: "Connect with user 'admin'" + +whilst the <code>=></code> operator is most useful when the expected data structure is known beforehand, to just unpack parts of it: + + config = {db: {user: 'admin', password: 'abc123'}} + + config => {db: {user:}} # will raise if the config's structure is unexpected + + puts "Connect with user '#{user}'" + # Prints: "Connect with user 'admin'" + +<code><expression> in <pattern></code> is the same as <code>case <expression>; in <pattern>; true; else false; end</code>. +You can use it when you only want to know if a pattern has been matched or not: + + users = [{name: "Alice", age: 12}, {name: "Bob", age: 23}] + users.any? {|user| user in {name: /B/, age: 20..} } #=> true + +See below for more examples and explanations of the syntax. + +== Patterns + +Patterns can be: + +* any Ruby object (matched by the <code>===</code> operator, like in +when+); (<em>Value pattern</em>) +* array pattern: <code>[<subpattern>, <subpattern>, <subpattern>, ...]</code>; (<em>Array pattern</em>) +* find pattern: <code>[*variable, <subpattern>, <subpattern>, <subpattern>, ..., *variable]</code>; (<em>Find pattern</em>) +* hash pattern: <code>{key: <subpattern>, key: <subpattern>, ...}</code>; (<em>Hash pattern</em>) +* combination of patterns with <code>|</code>; (<em>Alternative pattern</em>) +* variable capture: <code><pattern> => variable</code> or <code>variable</code>; (<em>As pattern</em>, <em>Variable pattern</em>) + +Any pattern can be nested inside array/find/hash patterns where <code><subpattern></code> is specified. + +Array patterns and find patterns match arrays, or objects that respond to +deconstruct+ (see below about the latter). +Hash patterns match hashes, or objects that respond to +deconstruct_keys+ (see below about the latter). Note that only symbol keys are supported for hash patterns. + +An important difference between array and hash pattern behavior is that arrays match only a _whole_ array: + + case [1, 2, 3] + in [Integer, Integer] + "matched" + else + "not matched" + end + #=> "not matched" + +while the hash matches even if there are other keys besides the specified part: + + case {a: 1, b: 2, c: 3} + in {a: Integer} + "matched" + else + "not matched" + end + #=> "matched" + +<code>{}</code> is the only exclusion from this rule. It matches only if an empty hash is given: + + case {a: 1, b: 2, c: 3} + in {} + "matched" + else + "not matched" + end + #=> "not matched" + + case {} + in {} + "matched" + else + "not matched" + end + #=> "matched" + +There is also a way to specify there should be no other keys in the matched hash except those explicitly specified by the pattern, with <code>**nil</code>: + + case {a: 1, b: 2} + in {a: Integer, **nil} # this will not match the pattern having keys other than a: + "matched a part" + in {a: Integer, b: Integer, **nil} + "matched a whole" + else + "not matched" + end + #=> "matched a whole" + +Both array and hash patterns support "rest" specification: + + case [1, 2, 3] + in [Integer, *] + "matched" + else + "not matched" + end + #=> "matched" + + case {a: 1, b: 2, c: 3} + in {a: Integer, **} + "matched" + else + "not matched" + end + #=> "matched" + +Parentheses around both kinds of patterns could be omitted: + + case [1, 2] + in Integer, Integer + "matched" + else + "not matched" + end + #=> "matched" + + case {a: 1, b: 2, c: 3} + in a: Integer + "matched" + else + "not matched" + end + #=> "matched" + + [1, 2] => a, b + [1, 2] in a, b + + {a: 1, b: 2, c: 3} => a: + {a: 1, b: 2, c: 3} in a: + +Find pattern is similar to array pattern but it can be used to check if the given object has any elements that match the pattern: + + case ["a", 1, "b", "c", 2] + in [*, String, String, *] + "matched" + else + "not matched" + end + +== Variable binding + +Besides deep structural checks, one of the very important features of the pattern matching is the binding of the matched parts to local variables. The basic form of binding is just specifying <code>=> variable_name</code> after the matched (sub)pattern (one might find this similar to storing exceptions in local variables in a <code>rescue ExceptionClass => var</code> clause): + + case [1, 2] + in Integer => a, Integer + "matched: #{a}" + else + "not matched" + end + #=> "matched: 1" + + case {a: 1, b: 2, c: 3} + in a: Integer => m + "matched: #{m}" + else + "not matched" + end + #=> "matched: 1" + +If no additional check is required, for only binding some part of the data to a variable, a simpler form could be used: + + case [1, 2] + in a, Integer + "matched: #{a}" + else + "not matched" + end + #=> "matched: 1" + + case {a: 1, b: 2, c: 3} + in a: m + "matched: #{m}" + else + "not matched" + end + #=> "matched: 1" + +For hash patterns, even a simpler form exists: key-only specification (without any sub-pattern) binds the local variable with the key's name, too: + + case {a: 1, b: 2, c: 3} + in a: + "matched: #{a}" + else + "not matched" + end + #=> "matched: 1" + +\Binding works for nested patterns as well: + + case {name: 'John', friends: [{name: 'Jane'}, {name: 'Rajesh'}]} + in name:, friends: [{name: first_friend}, *] + "matched: #{first_friend}" + else + "not matched" + end + #=> "matched: Jane" + +The "rest" part of a pattern also can be bound to a variable: + + case [1, 2, 3] + in a, *rest + "matched: #{a}, #{rest}" + else + "not matched" + end + #=> "matched: 1, [2, 3]" + + case {a: 1, b: 2, c: 3} + in a:, **rest + "matched: #{a}, #{rest}" + else + "not matched" + end + #=> "matched: 1, {b: 2, c: 3}" + +\Binding to variables currently does NOT work for alternative patterns joined with <code>|</code>: + + case {a: 1, b: 2} + in {a: } | Array + # ^ SyntaxError (variable capture in alternative pattern) + "matched: #{a}" + else + "not matched" + end + +Variables that start with <code>_</code> are the only exclusions from this rule: + + case {a: 1, b: 2} + in {a: _, b: _foo} | Array + "matched: #{_}, #{_foo}" + else + "not matched" + end + # => "matched: 1, 2" + +It is, though, not advised to reuse the bound value, as this pattern's goal is to signify a discarded value. + +== Variable pinning + +Due to the variable binding feature, existing local variable can not be straightforwardly used as a sub-pattern: + + expectation = 18 + + case [1, 2] + in expectation, *rest + "matched. expectation was: #{expectation}" + else + "not matched. expectation was: #{expectation}" + end + # expected: "not matched. expectation was: 18" + # real: "matched. expectation was: 1" -- local variable just rewritten + +For this case, the pin operator <code>^</code> can be used, to tell Ruby "just use this value as part of the pattern": + + expectation = 18 + case [1, 2] + in ^expectation, *rest + "matched. expectation was: #{expectation}" + else + "not matched. expectation was: #{expectation}" + end + #=> "not matched. expectation was: 18" + +One important usage of variable pinning is specifying that the same value should occur in the pattern several times: + + jane = {school: 'high', schools: [{id: 1, level: 'middle'}, {id: 2, level: 'high'}]} + john = {school: 'high', schools: [{id: 1, level: 'middle'}]} + + case jane + in school:, schools: [*, {id:, level: ^school}] # select the last school, level should match + "matched. school: #{id}" + else + "not matched" + end + #=> "matched. school: 2" + + case john # the specified school level is "high", but last school does not match + in school:, schools: [*, {id:, level: ^school}] + "matched. school: #{id}" + else + "not matched" + end + #=> "not matched" + +In addition to pinning local variables, you can also pin instance, global, and class variables: + + $gvar = 1 + class A + @ivar = 2 + @@cvar = 3 + case [1, 2, 3] + in ^$gvar, ^@ivar, ^@@cvar + "matched" + else + "not matched" + end + #=> "matched" + end + +You can also pin the result of arbitrary expressions using parentheses: + + a = 1 + b = 2 + case 3 + in ^(a + b) + "matched" + else + "not matched" + end + #=> "matched" + +== Matching non-primitive objects: +deconstruct+ and +deconstruct_keys+ + +As already mentioned above, array, find, and hash patterns besides literal arrays and hashes will try to match any object implementing +deconstruct+ (for array/find patterns) or +deconstruct_keys+ (for hash patterns). + + class Point + def initialize(x, y) + @x, @y = x, y + end + + def deconstruct + puts "deconstruct called" + [@x, @y] + end + + def deconstruct_keys(keys) + puts "deconstruct_keys called with #{keys.inspect}" + {x: @x, y: @y} + end + end + + case Point.new(1, -2) + in px, Integer # sub-patterns and variable binding works + "matched: #{px}" + else + "not matched" + end + # prints "deconstruct called" + "matched: 1" + + case Point.new(1, -2) + in x: 0.. => px + "matched: #{px}" + else + "not matched" + end + # prints: deconstruct_keys called with [:x] + #=> "matched: 1" + ++keys+ are passed to +deconstruct_keys+ to provide a room for optimization in the matched class: if calculating a full hash representation is expensive, one may calculate only the necessary subhash. When the <code>**rest</code> pattern is used, +nil+ is passed as a +keys+ value: + + case Point.new(1, -2) + in x: 0.. => px, **rest + "matched: #{px}" + else + "not matched" + end + # prints: deconstruct_keys called with nil + #=> "matched: 1" + +Additionally, when matching custom classes, the expected class can be specified as part of the pattern and is checked with <code>===</code> + + class SuperPoint < Point + end + + case Point.new(1, -2) + in SuperPoint(x: 0.. => px) + "matched: #{px}" + else + "not matched" + end + #=> "not matched" + + case SuperPoint.new(1, -2) + in SuperPoint[x: 0.. => px] # [] or () parentheses are allowed + "matched: #{px}" + else + "not matched" + end + #=> "matched: 1" + +These core and library classes implement deconstruction: + +* MatchData#deconstruct and MatchData#deconstruct_keys; +* Time#deconstruct_keys, Date#deconstruct_keys, DateTime#deconstruct_keys. + +== Guard clauses + ++if+ can be used to attach an additional condition (guard clause) when the pattern matches in +case+/+in+ expressions. +This condition may use bound variables: + + case [1, 2] + in a, b if b == a*2 + "matched" + else + "not matched" + end + #=> "matched" + + case [1, 1] + in a, b if b == a*2 + "matched" + else + "not matched" + end + #=> "not matched" + ++unless+ works, too: + + case [1, 1] + in a, b unless b == a*2 + "matched" + else + "not matched" + end + #=> "matched" + +Note that <code>=></code> and +in+ operator can not have a guard clause. +The following examples is parsed as a standalone expression with modifier +if+. + + [1, 2] in a, b if b == a*2 + +== Appendix A. Pattern syntax + +Approximate syntax is: + + pattern: value_pattern + | variable_pattern + | alternative_pattern + | as_pattern + | array_pattern + | find_pattern + | hash_pattern + + value_pattern: literal + | Constant + | ^local_variable + | ^instance_variable + | ^class_variable + | ^global_variable + | ^(expression) + + variable_pattern: variable + + alternative_pattern: pattern | pattern | ... + + as_pattern: pattern => variable + + array_pattern: [pattern, ..., *variable] + | Constant(pattern, ..., *variable) + | Constant[pattern, ..., *variable] + + find_pattern: [*variable, pattern, ..., *variable] + | Constant(*variable, pattern, ..., *variable) + | Constant[*variable, pattern, ..., *variable] + + hash_pattern: {key: pattern, key:, ..., **variable} + | Constant(key: pattern, key:, ..., **variable) + | Constant[key: pattern, key:, ..., **variable] + +== Appendix B. Some undefined behavior examples + +To leave room for optimization in the future, the specification contains some undefined behavior. + +Use of a variable in an unmatched pattern: + + case [0, 1] + in [a, 2] + "not matched" + in b + "matched" + in c + "not matched" + end + a #=> undefined + c #=> undefined + +Number of +deconstruct+, +deconstruct_keys+ method calls: + + $i = 0 + ary = [0] + def ary.deconstruct + $i += 1 + self + end + case ary + in [0, 1] + "not matched" + in [0] + "matched" + end + $i #=> undefined diff --git a/doc/syntax/precedence.rdoc b/doc/syntax/precedence.rdoc index 515626c74f..f0ca92b571 100644 --- a/doc/syntax/precedence.rdoc +++ b/doc/syntax/precedence.rdoc @@ -49,10 +49,14 @@ Unary <code>+</code> and unary <code>-</code> are for <code>+1</code>, <code>-1</code> or <code>-(a + b)</code>. Modifier-if, modifier-unless, etc. are for the modifier versions of those -keywords. For example, this is a modifier-unless expression: +keywords. For example, this is a modifier-unless statement: a += 1 unless a.zero? +Note that <code>(a if b rescue c)</code> is parsed as <code>((a if b) rescue +c)</code> due to reasons not related to precedence. See {modifier +statements}[control_expressions.rdoc#label-Modifier+Statements]. + <code>{ ... }</code> blocks have priority below all listed operations, but <code>do ... end</code> blocks have lower priority. diff --git a/doc/syntax/refinements.rdoc b/doc/syntax/refinements.rdoc index 7b3f0f6127..4095977284 100644 --- a/doc/syntax/refinements.rdoc +++ b/doc/syntax/refinements.rdoc @@ -7,12 +7,7 @@ changes. This can cause unintended side-effects or breakage of programs. Refinements are designed to reduce the impact of monkey patching on other users of the monkey-patched class. Refinements provide a way to extend a -class locally. - -Refinements are an experimental feature in Ruby 2.0. At the time of writing, -refinements are expected to exist in future versions of Ruby but the -specification of refinements may change. You will receive a warning the first -time you define or activate a refinement. +class locally. Refinements can modify both classes and modules. Here is a basic refinement: @@ -31,8 +26,7 @@ Here is a basic refinement: end First, a class +C+ is defined. Next a refinement for +C+ is created using -Module#refine. Refinements only modify classes, not modules so the argument -must be a class. +Module#refine. Module#refine creates an anonymous module that contains the changes or refinements to the class (+C+ in the example). +self+ in the refine block is @@ -42,20 +36,27 @@ Activate the refinement with #using: using M - x = C.new + c = C.new c.foo # prints "C#foo in M" == Scope -You may only activate refinements at top-level to the end of the file or in a -string passed to Kernel#eval, Kernel#instance_eval or Kernel#module_eval until -the end of the string. +You may activate refinements at top-level, and inside classes and modules. +You may not activate refinements in method scope. Refinements are activated +until the end of the current class or module definition, or until the end of +the current file if used at the top-level. + +You may activate refinements in a string passed to Kernel#eval. Refinements +are active until the end of the eval string. + +Refinements are lexical in scope. Refinements are only active within a scope +after the call to +using+. Any code before the +using+ statement will not have the +refinement activated. -Refinements are lexical in scope. When control is transferred outside the -scope the refinement is deactivated. This means that if you require or load a -file or call a method that is defined outside the current scope the refinement -will be deactivated: +When control is transferred outside the scope, the refinement is deactivated. +This means that if you require or load a file or call a method that is defined +outside the current scope the refinement will be deactivated: class C end @@ -78,7 +79,7 @@ will be deactivated: x.foo # prints "C#foo in M" call_foo(x) #=> raises NoMethodError -If a method is defined in a scope where a refinement is active the refinement +If a method is defined in a scope where a refinement is active, the refinement will be active when the method is called. This example spans multiple files: c.rb: @@ -140,6 +141,26 @@ In a file: end # activated here +In a class: + + # not activated here + class Foo + # not activated here + def foo + # not activated here + end + using M + # activated here + def bar + # activated here + end + # activated here + end + # not activated here + +Note that the refinements in +M+ are *not* activated automatically if the class ++Foo+ is reopened later. + In eval: # not activated here @@ -158,9 +179,9 @@ When not evaluated: end # not activated here -When defining multiple refinements in the same module, inside a refine block -all refinements from the same module are active when a refined method is -called: +When defining multiple refinements in the same module inside multiple +refine+ blocks, +all refinements from the same module are active when a refined method +(any of the +to_json+ methods from the example below) is called: module ToJSON refine Integer do @@ -186,14 +207,12 @@ called: p [{1=>2}, {3=>4}].to_json # prints "[{\"1\":2},{\"3\":4}]" + == Method Lookup When looking up a method for an instance of class +C+ Ruby checks: -* If refinements are active for +C+, in the reverse order they were activated: - * The prepended modules from the refinement for +C+ - * The refinement for +C+ - * The included modules from the refinement for +C+ +* The refinements of +C+, in reverse order of activation * The prepended modules of +C+ * +C+ * The included modules of +C+ @@ -202,12 +221,13 @@ If no method was found at any point this repeats with the superclass of +C+. Note that methods in a subclass have priority over refinements in a superclass. For example, if the method <code>/</code> is defined in a -refinement for Integer <code>1 / 2</code> invokes the original Fixnum#/ -because Fixnum is a subclass of Integer and is searched before the refinements -for the superclass Integer. +refinement for Numeric <code>1 / 2</code> invokes the original Integer#/ +because Integer is a subclass of Numeric and is searched before the refinements +for the superclass Numeric. Since the method <code>/</code> is also present +in child +Integer+, the method lookup does not move up to the superclass. -If a method +foo+ is defined on Integer in a refinement, <code>1.foo</code> -invokes that method since +foo+ does not exist on Fixnum. +However, if a method +foo+ is defined on Numeric in a refinement, <code>1.foo</code> +invokes that method since +foo+ does not exist on Integer. == +super+ @@ -222,19 +242,40 @@ When +super+ is invoked method lookup checks: Note that +super+ in a method of a refinement invokes the method in the refined class even if there is another refinement which has been activated in -the same context. +the same context. This is only true for +super+ in a method of a refinement, it +does not apply to +super+ in a method in a module that is included in a refinement. -== Indirect Method Calls +== Methods Introspection -When using indirect method access such as Kernel#send, Kernel#method or -Kernel#respond_to? refinements are not honored for the caller context during -method lookup. +When using introspection methods such as Kernel#method or Kernel#methods refinements are not honored. This behavior may be changed in the future. +== Refinement inheritance by Module#include + +When a module X is included into a module Y, Y inherits refinements from X. + +For example, C inherits refinements from A and B in the following code: + + module A + refine X do ... end + refine Y do ... end + end + module B + refine Z do ... end + end + module C + include A + include B + end + + using C + # Refinements in A and B are activated here. + +Refinements in descendants have higher precedence than those of ancestors. + == Further Reading -See http://bugs.ruby-lang.org/projects/ruby-trunk/wiki/RefinementsSpec for the +See https://github.com/ruby/ruby/wiki/Refinements-Spec for the current specification for implementing refinements. The specification also contains more details. - |
