Я могу вызвать любой метод на ноль, и это чувствует себя неправильно

14

Недавно я потратил много времени на отладку скрипта, и когда я наконец нашел проблему, это было из-за кода, который был похож на это:

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

Оказалось, проблема была в том $!.bar, что должно было быть $!barили $.bar. Я понял это.

Но почему это не умирает ?

Глядя на это более подробно, похоже , вопрос здесь в том , что я пытаюсь вызвать (несуществующий) метод barна $!, который в данный момент является , Nilпотому что не было никаких ошибок.

И похоже, что на самом деле я могу вызвать любой метод, который мне нужен, Nilи все они молча возвращаются Nil, включая такие вещи, как Nil.this-is-a-fake-methodи Nil.reverse-entropy(123).

Это особенность? Если так, в чем причина?

JJA
источник

Ответы:

13

Это задумано и задокументировано, да. Заголовок для Nil"Отсутствует значение или доброкачественная ошибка", и в документации класса упоминается

Любой вызов Nilметода, который не существует, и, следовательно, любая операция подписки, будет выполнена успешно и вернется Nil.

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

В Синопсисе 2 говорится: «Любой неопределенный вызов метода при Nilвозврате Nil, который Nilраспространяется вниз по цепочкам вызова метода. Аналогично любой операции подписки при Nilвозврате Nil», поэтому намерение, по-видимому, допускает выражения, такие как $foo.Bar()[0].Baz()не требующие проверки Nilна каждом шаге, или специальные «Nil-safe». "вызов метода и подписка операторов.

Hobbs
источник
4
Верно. И довольно давно, уже так же: github.com/rakudo/rakudo/commit/174727377f (декабрь 2013) См. Также спекуляцию: design.raku.org/S02.html#Nil
Элизабет Маттийсен
1
@ ElizabethMattijsen ах, я думаю, что у S02 есть ответ. «Любой неопределенный вызов метода при Nilвозврате Nil, который Nilраспространяется вниз по цепочке вызова метода». Таким образом, предполагается, что вы можете обойтись $foo.Bar().Baz().Blah()без нуля тестов на каждом шаге или специального ?.вида оператора (если вы правильно обрабатываете ноль в конце). Я отредактирую это, спасибо.
Хоббс
1
A, Nilкоторый не содержит ошибок и просто отбрасывает больше, Nilполучает довольно широкое применение в Obj-C и NS Frameworks, где он используется аналогичным образом - позволяет выполнять цепные вызовы, которые продолжают передаваться по Nil. Я не знаю точных штрафов за производительность исключений и их перехвата, но я предполагаю, что Nilсвязывание может быть более эффективным, если менее гибким.
user0721090601
1
(но это совершенно неосведомленное предположение, поэтому я рад, что lizmat или jnthn сказали мне, что я не прав и что мне нужно заткнуться :-))
user0721090601
2
В Nilклассе есть метод FALLBACK docs.raku.org/language/typesystem#index-entry-FALLBACK_(method) , который возвращает Nil. В основном, непосредственно перед тем, как генерируется исключение, потому что метод не может быть найден, проверка FALLBACKвыполняется и вызывается, если доступно.
Элизабет Маттийсен
5

Этот вопрос (и ответ Хоббса) также заставил меня чувствовать себя ... неловко: я в конце концов обнаружил https://docs.raku.org/language/traps : он объясняет, что присваивание Nilобычно приводит к другому значению Any. Следующее базовое взаимодействие REPL также демонстрирует это:

> 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

((разница между =и :=рассматривается здесь: https://docs.raku.org/language/containers#Binding ))

... так что общая идея заключается в том, что «отсутствующее значение или доброкачественный сбой» гораздо менее распространен в обычном коде, чем я (и, возможно, вы тоже?) внезапно опасался: однако, это происходит, когда вы собираетесь работать с регулярным выражением соответствует непосредственно и в вашей конкретной случайной ситуации, связанной с непосредственным вызовом методов $!.

Это сделало меня более осведомленным о разнице между Nilи Any, и предоставленное обоснование имело больше смысла для меня.

хромис
источник
2
Nilустанавливает контейнер в состояние по умолчанию. Вы можете изменить значение по умолчанию. my $foo is default(42) = 5; $foo = Nil; say $foo; # 42
Брэд Гилберт