Documenting lesser-known features in Ruby
= super fancy case statements
…implemented by Kazuki Tsujimoto (k-tsj)
…in the makes since 2012 (pattern-match gem)
Pattern matching is experimental, and the behavior may change in future versions of Ruby!
Deactivate withWarning[:experimental] = false
Or on CLI$ ruby -W:no-experimental
Old case Statement
case forty_two
when 42
puts "found number 42"
when /42/
puts "matched 42"
else
puts "no 42"
end
New case Statement
case forty_two
in 42
puts "found number 42"
in /42/
puts "matched 42"
else
puts "no 42"
end
THE SAME!!!
case
⇒ in
in
is no stranger to us…Warm-up question:
Where else is it used in the Ruby language
for n in [2,3,4] do puts n end
uses ===
method for checking a condition
no mix and match between in
and when
"empty" case statements don't work with in
without else
clause, one condition must match or NoMatchingPatternError
case forty_two
in 42 if false
puts "found number 42"
in /42/ unless forty_two.length <3
puts "matched 42"
else
puts "no answer"
end
Syntax OK
def ask
0b101010 in answer
answer
end
ask #=> 42
Matching assign variables!
Even without a surrounding case-statement!
[1, 2, 3, 4] in [first, second, *other]
Syntax OK
Think: Put [1, 2, 3, 4] into [first, second, *other]
{
verb: "CREATE",
endpoint: "/feierabend",
authed: true,
} in { verb:, endpoint: }
verb #=> "CREATE"
endpoint #=> "/feierabend"
[[1, 2, 3, 4], [:a, :b]] in [[n1, n2], symbols]
NoMatchingPatternError
[[1, 2, 3, 4], [:a, :b]] in [[n1, n2, *other], symbols]
case [1, 2, 2, 3, 4]
in [1, 2, 2, a, 4] if false
p :first
in [1, b, b, Integer, Integer]
p :second
else
p :third
end
SyntaxError (duplicated variable name)_
instead of b
will work
case [1, 2, 2, 3, 4]
in [1, 2, 2, a, 4] if false
p :first
in [1, b, c, Integer, Integer]
p [:second, b]
else
p :third
end
# => [:second, 2]
…and what's the value of a
?
a #=> 4
home = /Berlin|Potsdam/
case {
cities: ["Berlin", "Porto", "Tokio"],
groups: [["Andrea", "Kim"], ["Chris", "Sascha"]],
reasons: ["Unknown", "Meaningfulness"],
}
in { cities: [^home, *other_cities],
groups: [[a, b] => first, *],
reasons: [important] }
p [:lets_go_to?, other_cities, first]
in { cities: }
p [:listing_all, cities]
end
[:listing_all, ["Berlin", "Porto", "Tokio"]]
→ ??? ←
case [1,2,3]
in Array(a,b,c)
p [:first, a, b, c]
in Array[a,b,c]
p [:second, a, b, c]
in [a,b,c]
p [:third, a, b, c]
end
#=> [?, 3, 2, 1]
class Array
def deconstruct
reverse
end
end
speakerdeck.com/k_tsj/pattern-matching-new-feature-in-ruby-2-dot-7
How to produce this output?
[1, 2, 3, 4].__________ { |n|
n.even? ? n/2 : nil
} #=> [1, 2]
How to produce this output?
[1, 2, 3, 4].filter_map { |n|
n.even? ? n/2 : nil
} #=> [1, 2]
How to code golf this?
[1, 2, 3, 4].filter_map { _1.even? ? _1/2 : nil }
[1,2,3,4].filter_map{_1%2<1&&_1/2}
What will happen?
send def no ** nil; end, nil: :no
ArgumentError (no keywords accepted)
Commentary by Koichi Sasada and Yusuke Endoh sourcediving.com/ruby-2-7-news-commentary-by-cookpads-full-time-ruby-comitters-bdbaacb36d0c
You see .
(a dot) in some Ruby code
How many different meanings can it have?
.
? (1)1.42
(floating point number)
.
? (2)object.method
(method call)
.
? (2-3)object.()
(method call to a method named "call")
.
? (2-4)def object.method() end
(method definition)
.
? (3-5)"."
(part of string literal)
.
? (4-6)%.string.
(string delimiter)
.
? (5-7):"."
(part of symbol name)
.
? (6-8)/./
(regex literal, match-any)
.
? (6-9)/\./
(regex literal, escaped)
.
? (6-10)#.
(part of comment)
.
? (7-11)1..42
(range, inclusive)
.
? (7-12)1...42
(range, exclusive)
.
? (7-14)1..
1...
(endless range, inclusive + exclusive, new in 2.6)
.
? (7-16)..42
...42
("beginless" range, inclusive + exclusive, new in 2.7)
.
? (7-18)p...;
p..;
(nilistic range, inclusive + exclusive, new in 2.7)
.
? (8-19)def puts!(...)
puts(...)
puts "!!!"
end
putz "hey" #=>
hey
!!!
(argument forwarding, new in 2.7)
.
? (9-20)BONUS ROUND
$.
(part of this special variable name, which returns line number of input read from $STDIN)