Por que um objeto Regexp é considerado "falso" no Ruby?

16

Ruby tem uma ideia universal de " veracidade " e " falsidade ".

Rubi faz ter duas classes específicas de objetos booleano, TrueClasse FalseClass, com as instâncias únicas indicadas pelas variáveis especiais truee false, respectivamente.

No entanto, truthiness e falsiness não estão limitados a instâncias dessas duas classes, o conceito é universal e se aplica a cada objeto em Ruby. Cada objeto é ou truthy ou Falsas . As regras são muito simples. Em particular, apenas dois objetos são falsos :

Todo outro objeto é verdadeiro . Isso inclui até objetos que são considerados falsos em outras linguagens de programação, como

Essas regras são incorporadas ao idioma e não podem ser definidas pelo usuário. Não há to_boolconversão implícita ou algo semelhante.

Aqui está uma citação da ISO Ruby Language Specification :

6.6 Valores booleanos

Um objeto é classificado em um objeto trueish ou falso .

Somente false e nil são objetos falsos. false é a única instância da classe FalseClass(consulte 15.2.6), para a qual uma expressão falsa avalia (consulte 11.5.4.8.3). nil é a única instância da classe NilClass(consulte 15.2.4), para a qual uma expressão nula é avaliada (consulte 11.5.4.8.2).

Objetos diferentes de false e nil são classificados em objetos trueish. true é a única instância da classe TrueClass(consulte 15.2.5), para a qual uma expressão true é avaliada (consulte 11.5.4.8.3).

O executável Ruby / Spec parece concordar :

it "considers a non-nil and non-boolean object in expression result as true" do
  if mock('x')
    123
  else
    456
  end.should == 123
end

De acordo com essas duas fontes, eu assumiria que Regexps também são verdadeiros , mas de acordo com meus testes, eles não são:

if // then 'Regexps are truthy' else 'Regexps are falsy' end
#=> 'Regexps are falsy'

Eu testei isso no YARV 2.7.0-preview1 , no TruffleRuby 19.2.0.1 e no JRuby 9.2.8.0 . Todas as três implementações concordam entre si e discordam da ISO Ruby Language Specification e da minha interpretação do Ruby / Spec.

Mais precisamente, os Regexpobjetos resultantes da avaliação de Regexp literais são falsos , enquanto os Regexpobjetos resultantes de alguma outra expressão são verdadeiros :

r = //
if r then 'Regexps are truthy' else 'Regexps are falsy' end
#=> 'Regexps are truthy'

Isso é um bug ou comportamento desejado?

Jörg W Mittag
fonte
O interessante é que Regex.new("a")é verdade.
Mrzasa 11/11/19
!!//é falso, mas !!/r/é verdadeiro. Estranho mesmo.
max
O @max !!/r/produz falsepara mim usando (RVM) Ruby 2.4.1.
3limin4t0r 11/11/19
Desculpe meu mau @ 3limin4t0r. Você está certo. Eu devo ter feito algo realmente estúpido como deixar de fora um ponto de exclamação.
max
2
Uma hipótese, acho que //em if // thené interpretado como um teste (um atalho para if //=~nil then) (que é sempre Falsas qualquer que seja o padrão) e não como uma instância Regexp.
Casimir et Hippolyte

Respostas:

6

Isso não é um bug. O que está acontecendo é que Ruby está reescrevendo o código para que

if /foo/
  whatever
end

efetivamente se torna

if /foo/ =~ $_
  whatever
end

Se você estiver executando esse código em um script normal (e não -eestiver usando a opção), deverá receber um aviso:

warning: regex literal in condition

Isso provavelmente é um pouco confuso na maioria das vezes, e é por isso que o aviso é dado, mas pode ser útil para uma linha usando a -eopção Por exemplo, você pode imprimir todas as linhas correspondentes a uma determinada regexp de um arquivo com

$ ruby -ne 'print if /foo/' filename

(O argumento padrão para também printé $_.)

mate
fonte
Veja também -n, -p, -ae -lopções, bem como o punhado de métodos de Kernel que estão disponíveis apenas quando -nou -psão utilizadas ( chomp, chop, gsube sub).
Matt
Há também uma segunda parte do analisador em que esse aviso é emitido. Eu não sei o que está acontecendo lá.
Matt
Eu acredito que "segunda parte" é a que realmente se aplica a esta questão. NODE_LITcom o tipo T_REGEXP. O que você postou na sua resposta é para um literal dinâmicoRegexp , ou seja, um Regexpliteral que usa interpolação, por exemplo /#{''}/.
Jörg W Mittag
@ JörgWMittag Acho que você está certo. Procurando no compilador e no bytecode gerado, parece que, no caso da regexp dinâmica, a árvore de análise é reescrita para incluir explicitamente $_como um nó que o compilador lida normalmente, enquanto no caso estático é tratado pelo compilador. O que é uma pena para mim, porque "ei, você pode ver onde a árvore de análise é reescrita aqui" contribui para uma boa resposta.
Matt
4

Este é o resultado de (até onde eu sei) um recurso não documentado da linguagem ruby, que é melhor explicado por esta especificação :

it "matches against $_ (last input) in a conditional if no explicit matchee provided" do
  -> {
    eval <<-EOR
    $_ = nil
    (true if /foo/).should_not == true
    $_ = "foo"
    (true if /foo/).should == true
    EOR
  }.should complain(/regex literal in condition/)
end

Geralmente, você pode considerar $_a "última string lida por gets"

Para tornar as coisas ainda mais confusas, $_(junto com $-) não é uma variável global; tem escopo local .


Quando um script ruby ​​é iniciado $_ == nil,.

Então, o código:

// ? 'Regexps are truthy' : 'Regexps are falsey'

Está sendo interpretado como:

(// =~ nil) ? 'Regexps are truthy' : 'Regexps are falsey'

... O que retorna falsey.

Por outro lado, para um regexp não literal (por exemplo, r = //ou Regexp.new('')), essa interpretação especial não se aplica.

//é verdade; assim como todos os outros objetos em ruby ​​além de nile false.


A menos que seja executado um script ruby ​​diretamente na linha de comando (ou seja, com o -esinalizador), o analisador ruby ​​exibirá um aviso contra esse uso:

aviso: regex literal na condição

Você pode fazer uso desse comportamento em um script, com algo como:

puts "Do you want to play again?"
gets
# (user enters e.g. 'Yes' or 'No')
/y/i ? play_again : back_to_menu

... Mas seria mais normal atribuir uma variável local ao resultado getse executar a verificação de expressão regular contra esse valor explicitamente.

Não conheço nenhum caso de uso para executar essa verificação com um regex vazio , especialmente quando definido como um valor literal. O resultado que você destacou realmente pegaria a maioria dos desenvolvedores de rubi desprevenidos.

Tom Lord
fonte
Eu apenas usei o condicional como exemplo. !// #=> truetem o mesmo comportamento e não é condicional. Não consegui encontrar nenhum contexto booleano (condicional ou não), onde ele se comporta conforme o esperado.
Jörg W Mittag
@ JörgWMittag Você quer dizer, por exemplo, !// ? true : falseretornos true? Acho que este é o mesmo ponto novamente - ele está sendo interpretado como:!(// =~ nil) ? true : false
Tom Lord
Se você definir manualmente $_ = 'hello world'antes de executar o código acima, deverá obter um resultado diferente - porque // =~ 'hello world', mas não corresponde nil.
Tom Lord
Não, quero dizer, !// sem a avaliação condicional de true. A especificação que você citou é sobre um Regexpliteral em uma condicional, mas neste exemplo, não há condicional, portanto, essa especificação não se aplica.
Jörg W Mittag
2
Ah .. Sim, muito surpreendente. O comportamento parece estar vinculado, no entanto: puts !//; $_ = ''; puts !//- Suponho que o analisador o expanda como uma macro; não precisa necessariamente estar dentro de um condicional?
Tom Lord