8

According to Raku documentation, routine first returns Nil when no values match. However, why doesn't the line say ($result === Nil) ; (below) print True?

sub MAIN()
  {
  my @myNumberList is Array[Int] = [2, 2, 5, 7] ;
  my $result = @myNumberList.first( {$_ > 10} ) ;
  say '$result' ;
  say $result ;
  say $result.WHAT ;
  say ($result === Nil) ;
  } # end sub MAIN

Program output is …

$result
(Any)
(Any)
False

Update

My goal was to determine whether a list or an array contains one or more elements satisfying a given predicate. In the following sample program, the elems routine returns a value that is greater than 0 if and only if the given list or array contains at least one element satisfying the predicate given in the corresponding call to grep .

sub MAIN()
  {
  my $myList = (2, 2, 5, 7) ;
  say '$myList' ;
  say $myList ;
  say $myList.WHAT ;

  my $grepResult1 = $myList.grep( * > 10, :p ) ;
  say '$grepResult1' ;
  say $grepResult1 ;
  say $grepResult1.WHAT ;
  say ($grepResult1.elems <= 0) ;

  my $grepResult2 = $myList.grep( * > 4, :p ) ;
  say '$grepResult2' ;
  say $grepResult2 ;
  say $grepResult2.WHAT ;
  say ($grepResult2.elems <= 0) ;

  my @myArray = [2, 2, 5, 7] ;
  say '@myArray' ;
  say @myArray ;
  say @myArray.WHAT ;

  my $grepResult3 = @myArray.grep( * > 10, :p ) ;
  say '$grepResult3' ;
  say $grepResult3 ;
  say $grepResult3.WHAT ;
  say ($grepResult3.elems <= 0) ;
  } # end sub MAIN

Program output is …

$myList
(2 2 5 7)
(List)
$grepResult1
()
(Seq)
True
$grepResult2
(2 => 5 3 => 7)
(Seq)
False
@myArray
[2 2 5 7]
(Array)
$grepResult3
()
(Seq)
True

user3134725
  • 1,003
  • 6
  • 12

2 Answers2

6

Interesting question that appears to be (somewhat) addressed on the "Traps To Avoid" documentation page. See the specific subheading: "Assignment of Nil can produce a different value, usually 'Any' ". Also, the Raku documentation page on class Nil .

The short answer--as near as I can surmise--is to use smartmatching with a Junction on the right-hand-side:

Sample Code:

sub MAIN()
  {
  my @myNumberList is Array[Int] = [2, 2, 5, 7] ;
  my $result = @myNumberList.first( {$_ > 10} ) ;
  say '$result' ;
  say $result ;
  say $result.WHAT ;

"\n".say;

  say  Nil ~~ all($result);  #True
  say  Nil ~~ any($result);  #True
  say  Nil ~~ one($result);  #True
  say  Nil ~~ none($result); #False

"\n".say;

  my $result2 = @myNumberList.first( {$_ < 10} ) ;
  say  Nil ~~ all($result2);  #False
  say  Nil ~~ any($result2);  #False
  say  Nil ~~ one($result2);  #False
  say  Nil ~~ none($result2); #True

  } # end sub MAIN

Returns:

$result
(Any)
(Any)


True
True
True
False


False
False
False
True

Alternatively, you can simplify the above code (and clean up your output) using .defined:

  1. for result above (where there are no matching values) say $result if $result.defined; returns nothing;

  2. for result2 above (where the first matching value is 2) say $result2 if $result2.defined; returns 2.

For further reading to understand how the smartmatch solution(s) posted above came about, look at the following Github issue: "In general, the left side of ~~ should not be auto-threading".

[Also, (at the risk of confusing the reader), take a look at the Raku code posted on the following Perl5 doc page: https://perldoc.perl.org/perlsyn#Differences-from-Raku ].

ADDENDUM: For a discussion on the 'do-something-only-if-defined' idiom in Raku, see Damian Conway's talk "On The Shoulders of Giants", which includes his code for a user-defined ?= ("assign-if-defined") operator.

outis
  • 75,655
  • 22
  • 151
  • 221
jubilatious1
  • 1,999
  • 10
  • 18
2

TL;DR Yes, first returns Nil when no values match. One way to get what you want is to bind the result (with :=) instead of assigning it (with =).

A simple solution

Switch from the assignment operator (=) to the binding operator (:=):

sub MAIN()
  {
  my @myNumberList is Array[Int] = [2, 2, 5, 7] ;
  my $result := @myNumberList.first( {$_ > 10} ) ;     # <-- Add colon
  say '$result' ;
  say $result ;
  say $result.WHAT ;
  say ($result === Nil) ;
  } # end sub MAIN

displays:

$result
Nil
Nil
True

What's happening in your original code

In Raku, doesn't routine first return Nil when no values match?

Yes. That's not the problem.

Nil represents "Absence of a value ...".¹

So don't think of it as a value like, say, 42 or 'string' or an empty list².

Assigning Nil to a variable resets the variable's value to its default:

my $foo is default(42) = Nil;
say $foo; # 42

The default default value for a variable is Any³, so:

my $bar = Nil;
say $bar; # (Any)

That's what happened in your original code.

Options

You could set $result's default to Nil so that when you assign Nil to it you get what you want:

my $result is default (Nil) = @myNumberList.first( {$_ > 10} ) ;

But that's cumbersome. And seldom desirable.

Usually, if you want a variable to keep a Nil, you just bind it instead of assigning it:

my $qux := Nil;      # <-- use `:=` instead of `=`
say $qux; # Nil

Just set your mental model to match what Raku's doing and you'll find it's all really simple.

Footnotes

¹ Per its doc, Nil is "Absence of a value or a benign failure". The "Absence of a value" aspect applies if you call first to find the first value in some list of values that matches a predicate, but there were no such values. The "benign failure" applies if you call first and it encounters some error condition and either decides it's benign but ought terminate its processing, or throws an exception that gets handled such that further evaluation continues with the code that receives the value returned by first.

As you read this footnote you might think these two cases would best be handled separately. But if you think further you will hopefully see that they work beautifully as two sides of the same coin. Any called code can return Nil, and calling code knows that the result of the call is an absence of a value. This may be due to entirely normal processing which yields no value, or due to processing that encountered an error, but that error was either considered by the called code as so benign as to not be worth reporting to the caller other than by returning Nil, or resulted in an exception being thrown that was subsequently handled to clear the error condition -- thus returning processing to normal status. Raku's elegant and practical Nil is yet another reason why I ❤ Raku.

² An empty list is a still a value. Albeit an empty one.

³ Any is a type. When treated as a "value" it's a type object representing an undefined value of type Any or a subtype thereof. It makes a natural default default value for variables and parameters.

⁴ I suspect I offended folk by writing my "Just ..." sentence. I thought about deleting or editing it, but then folk wouldn't have context for @jubilatious1's comment and my point, which I think is a good one that @jubilatious1 misunderstood, would be lost. I'm leaving it in with this footnote for now in the hope someone will engage with me if they think it is a problem and/or if it would be a good idea for me to remove it or, conversely, unstrike it.

raiph
  • 31,607
  • 3
  • 62
  • 111
  • *"Just set your mental model to match what Raku's doing and you'll find it's all really simple."* The OP is trying to figure out what Raku is doing. If you're going to go into so-called "absence-of-values", then that necessarily would require discussion of the empty List, denoted with the keyword `Empty` or simply the empty parens `()`. Then...we'll see isn't all so simple! – jubilatious1 Jul 02 '22 at 00:36
  • "The OP is trying to figure out what Raku is doing." Yes. And imo that's not so simple. And this is so even if what Raku is doing is really simple. "If you're going to go into so-called "absence-of-values"" Well, I didn't go into that so much as the Raku design did, and `Nil` is a central part of that. "then that necessarily would require discussion of the empty `List`" Imo it's not necessary *to understand Raku's model*. An empty `List` is a value, a `List`, albeit an empty one. "isn't all so simple!" Imo Raku's model is but human models aren't. Surmise yields surprise until we let go! – raiph Jul 02 '22 at 11:11
  • If a value and/or values is/are truly `Nil`, meaning you have no knowlege of it/them, (and/or it/they are `NA` "not-available"), then how can you say with any certainty that you have one-and-only-one `Nil` element? – jubilatious1 Jul 02 '22 at 11:33
  • "If a value and/or values is/are truly `Nil`, meaning you have no knowlege of it/them, (and/or it/they ..." What "value", "it", "them", or "they"? Imagine the universe does not exist. There is nothing at all. `Nil` represents this notion of nothingness. There's nothing to count. There's not one-and-only-one this or that. There's not zero of "them". There's not even none. There's just an absence of anything. Now consider code that *expects* to be given a "value". `Nil` represents the concept "I'm sorry, there is no value, there is nothing for you, please handle this situation as you see fit". – raiph Jul 02 '22 at 16:42
  • https://unix.stackexchange.com/a/706668/227738 – jubilatious1 Jul 05 '22 at 02:31