Dê uma olhada em uma expressão case
em Ruby:
case x
when 1, 2
"É 1 ou 2"
when 3..10
"É um número de 3 a 10"
when BigDecimal
"É uma instância de BigDecimal"
when /bot/
"É uma string que corresponde à expressão regular /bot/"
end
Comparadas a muitas linguagens que possuem switch statements, as expressões case
do Ruby são muito mais flexíveis, correspondendo não apenas em casos de estrita igualdade, mas também em uma espécie de filiação ou correspondência: 2
é um membro do intervalo 1..3
; 5
é um membro da classe Integer
; "thoughtbot"
corresponde à expressão regular /bot/
.
Este poder vem do operador ===
(ou operador de igualdade de caso, três iguais, igual triplo, como preferir). Cada argumento passado para when
é comparado com x
usando esse operador. O Ruby interpretará a expressão case
acima como se fosse:
if 1 === x || 2 === x
"É 1 ou 2"
elsif 3..10 === x
"É um número de 3 a 10"
elsif BigDecimal === x
"É uma instância de BigDecimal"
elsif /bot/ === x
"É uma string que corresponde à expressão regular /bot/"
end
A classe Object
, herdada pela maioria das classes Ruby, implementa o método ===
como um alias para ==
. A documentação para Object
descreve-o como:
Igualdade de caso – Para a classe
Object
, efetivamente o mesmo que chamar#==
, mas normalmente sobrescrito por descendentes para fornecer semântica significativa em expressõescase
.
Como esses descendentes sobrescrevem o método ===
?`
Module
A classe Module
, herdada pela própria classe Class
, implementa ===
como uma verificação is_a?
no objeto fornecido. Ele retornará true
se a classe ou módulo estiver na árvore ancestral do objeto fornecido.
Enumerable === [1, 2, 3] # => true
Array === "A string" # => false
String === "A string" # => true
Range
Intervalos implementam ===
com o mesmo comportamento que cover?
.
(1..10) === 3 # => true
(5..20) === 2 # => false
(5..20) === 15 # => true
(1..10) === 3.5 # => true
(5..20) === 2 # => false
Set
Um conjunto implementa ===
verificando se o objeto fornecido é um membro desse conjunto. Efetivamente, o mesmo que include?
.
Set[1, 2, 3] === 2 # => true
Set[1, 2, 3] === "😀" # => false
Regexp
Objetos Regexp
retornarão true
em ===
quando, dada uma string, a expressão regular corresponder à string. É próximo à implementação de match?
.
/bot/ === "thoughtbot" # => true
URI::MailTo::EMAIL_REGEXP === "https://thoughtbot.com" # => false
IPAddr
Objetos IPAddr
respondem a ===
como um equivalente a include?
. Eles retornarão true
se o IP fornecido for igual ou estiver dentro do intervalo.
subnet = IPAddr.new("192.168.2.0/24")
ip = IPAddr.new("192.168.2.100")
subnet.include?(ip) # => true
subnet === ip # => true
Proc
Objetos Proc
, incluindo lambdas, têm maneiras demais de serem chamados, e ===
é mais uma delas.
Prime = ->(n) { n >= 2 && (2..Math.sqrt(n)).none? { |i| n % i == 0 } }
Prime === 3 # => true
Prime === 4 # => false # 4 não é um número primo
Qualquer classe que você escrever
Agora que você sabe como as expressões case
funcionam nos bastidores, você pode implementar o operador de igualdade de caso em suas próprias classes, fazendo correspondências de maneiras significativas, como por exemplo se um ponto GPS está dentro de uma área:
class Map::Area
# ...
def ===(other)
if other.respond_to?(:latitude) && other.respond_to?(:longitude)
# A coordenada fornecida está dentro desta área?
else
other == self
end
end
end
estado = Map::Area.new(lista_de_coordenadas_gps_para_desenhar_a_area)
cidade = Map::Point.new(-9.648139, -35.717239)
estado === cidade # => true
Além das expressões case
Você pode estar se perguntando que, como as expressões case
são um code smell que seu código tem conhecimento demais, o método ===
não é tão útil. Mas há usos do operador de igualdade de caso além de seu propósito inicial.
O módulo Enumerable
implementa muitos métodos que o usam. Um exemplo interessante é #grep
. Ele retornará os elementos enumeráveis que correspondem ao padrão fornecido usando o método ===
, então:
[1, "a", 3].grep(Integer) # => [1, 3]
["foo", "bar", "baz"].grep(/a/) # => ["bar", "baz"]
(1..10).grep(3..8) # => [3, 4, 5, 6, 7, 8]
(1..15).grep(Prime) # => [2, 3, 5, 7, 11, 13]
lugares.grep(area) # => [local_1, local_3, ...]
Outros métodos de Enumerable
que usam o operador de igualdade de caso são: #all?
, #none?
, #one?
, #any?
, #grep_v
, #slice_after
e #slice_before
.