Posso chamar qualquer método em Nil e isso parece errado

14

Passei um tempo considerável depurando um script recentemente e, quando finalmente encontrei o problema, foi por causa do código que se parecia com isso:

class Foo {
    has $.bar;
    method () {
        # do stuff
        $!.bar;
    }
}

Acabou que o problema era com isso $!.bar, que deveria ter sido um $!barou outro $.bar. Eu entendi isso.

Mas por que isso não morre ?

Olhando para isso com mais detalhes, parece que a questão aqui é que eu estou tentando chamar um método (inexistente) barem $!, que neste momento é Nilporque não houve nenhum erro.

E parece que eu posso realmente chamar qualquer método que eu queira Nile todos eles retornam silenciosamente Nil, incluindo coisas como Nil.this-is-a-fake-methode Nil.reverse-entropy(123).

Isso é um recurso? Se sim, qual é a lógica?

jja
fonte

Respostas:

13

É planejado e documentado, sim. A manchete Nilé "Ausência de valor ou falha benigna" e a documentação da classe menciona

Qualquer chamada Nilde método que não exista e, conseqüentemente, qualquer operação de assinatura será bem-sucedida e retornará Nil.

say Nil.ITotallyJustMadeThisUp;  # OUTPUT: «Nil␤» 
say (Nil)[100];                  # OUTPUT: «Nil␤» 
say (Nil){100};                  # OUTPUT: «Nil␤»

A Sinopse 2 declara "Qualquer método indefinido que chama Nilretornos Nil, de modo que se Nilpropaga para baixo de cadeias de chamadas de método. Da mesma forma que qualquer operação de assinatura em Nilretornos Nil", portanto, a intenção parece permitir expressões como $foo.Bar()[0].Baz()sem exigir verificações Nila cada etapa ou especial "Nil-safe "operadores de chamada e assinatura de método.

hobbs
fonte
4
De fato. E já faz muito tempo: github.com/rakudo/rakudo/commit/174727377f (dezembro de 2013) Veja também a especulação associada: design.raku.org/S02.html#Nil
Elizabeth Mattijsen
11
@ElizabethMattijsen ah, acho que S02 tem a resposta. "Qualquer método indefinido chama Nilretornos Nil, para que Nilpropaga cadeias de chamadas de método." Portanto, pretende-se que você possa fazer isso $foo.Bar().Baz().Blah()sem precisar de testes nulos a cada etapa ou de um ?.tipo especial de operador (desde que você lide corretamente com nil no final). Vou editar isso, obrigado.
hobbs 10/02
11
Um Nilque não erra e apenas retrocede mais, Nilrecebe um uso bastante extenso no Obj-C e nos NS Frameworks, onde é usado de maneira semelhante - permite chamadas em cadeia que continuam passando Nil. Não conheço as penalidades exatas de desempenho das exceções e as apanhamos, mas meu palpite é que o Nilencadeamento pode ser mais eficiente, se menos flexível.
user0721090601 10/02
11
(mas isso é um palpite totalmente desinformado, por isso estou feliz que lizmat ou jnthn me digam que estou errado e que preciso calar a boca :-))
user0721090601
2
A Nilclasse possui um método FALLBACK docs.raku.org/language/typesystem#index-entry-FALLBACK_(method) , que retorna Nil. Basicamente, pouco antes de uma exceção ser lançada porque um método não pôde ser encontrado, uma verificação FALLBACKé feita e chamada se disponível.
Elizabeth Mattijsen
5

Essa pergunta (e a resposta de hobbs) também me deixou desconfortável: acabei encontrando https://docs.raku.org/language/traps : explica que atribuir Nilgeralmente produz um valor diferente Any. A seguinte interação básica do REPL também demonstra isso:

> my $foo = Nil
(Any)

> $foo.bar
No such method 'bar' for invocant of type 'Any'
  in block <unit> at <unknown file> line 1

> my $bar := Nil
Nil

> $bar.baz
Nil

((a diferença entre =e :=é abordada aqui: https://docs.raku.org/language/containers#Binding ))

... então a ideia geral é que o 'valor ausente ou falha benigna' é muito menos difundido no código regular do que eu (e talvez você também?) subitamente temia: ocorre no entanto, quando você trabalha com regex corresponde diretamente e em sua situação acidental específica relacionada à chamada direta de métodos em $!.

Isso me deixou mais consciente da diferença entre Nile Any, e a lógica fornecida fazia mais sentido para mim.

chromis
fonte
2
Nildefine um contêiner para seu estado padrão. Você pode alterar o valor padrão. my $foo is default(42) = 5; $foo = Nil; say $foo; # 42
Brad Gilbert