Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

I can call any method on Nil and this feels wrong

Tags:

raku

I spent considerable time debugging a script recently, and when I finally found the problem it was because of code that looked like this:

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

It turned out the problem was with that $!.bar, which should have been either $!bar or $.bar. I get this.

But why doesn't this die?

Looking at this in more detail, it looks like the issue here is that I'm trying to call a (non-existent) method bar on $!, which at this point is Nil because there haven't been any errors.

And it looks like I can actually call any method I want on Nil and they all silently return Nil, including stuff like Nil.this-is-a-fake-method and Nil.reverse-entropy(123).

Is this a feature? If so, what's the rationale?

like image 312
jja Avatar asked Feb 10 '20 17:02

jja


2 Answers

It's intended and documented, yes. The headline for Nil is "Absence of a value or a benign failure", and the class documentation mentions

Any method call on Nil of a method that does not exist, and consequently, any subscripting operation, will succeed and return Nil.

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

Synopsis 2 states "Any undefined method call on Nil returns Nil, so that Nil propagates down method call chains. Likewise any subscripting operation on Nil returns Nil", so the intent seems to be allowing expressions like $foo.Bar()[0].Baz() without requiring checks for Nil at every step, or special "Nil-safe" method call and subscripting operators.

like image 87
hobbs Avatar answered Oct 08 '22 19:10

hobbs


This question (and hobbs' answer) also made me feel... uneasy: I eventually found https://docs.raku.org/language/traps: it explains that assigning Nil produces a different value, usually Any. The following basic REPL interaction also demonstrates this:

> 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

(( the difference between = and := is covered here: https://docs.raku.org/language/containers#Binding ))

...so the general idea is that the 'absent value or benign failure' is far less widespread in regular code than I (and perhaps you as well?) suddenly feared: it does occur however, when you are going to work with regex matches directly and in your particular accidental situation related to directly invoking methods on $!.

This made me more aware about the difference between Nil and Any, and the provided rationale made more sense to me.

like image 22
chromis Avatar answered Oct 08 '22 21:10

chromis