Search code examples
raku

Mapping a List with a Hash argument


In the docs for class List, it says:

routine map

multi method map(Hash:D \hash)  
multi method map(Iterable:D \iterable)   
multi method map(|c) 
multi method map(\SELF: █; :$label, :$item)   multi sub map(&code, +values)
multi sub map(&code, +values)

In the first multi method clause, I think the signature says that the method takes one argument, which is an instance of the Hash class (Hash:D). The argument will be assigned to a sigil-less variable \hash (which is a constant), which means you know the hash in the caller cannot be changed by the method.

Based on the method name, map, and the hash argument, it seems likely that the hash will map the elements of a list to the corresponding values in the hash. Let's try it:

[106] > my %hash = %{1 => 'apple', 2 => 'tree', 3 => 'octopus'};
{1 => apple, 2 => tree, 3 => octopus}

[107] > (1, 2, 3).map(%hash);
Cannot map a List using a Hash
Did you mean to add a stub ({ ... }) or did you mean to .classify?
  in block <unit> at <unknown file> line 1
  in any <main> at /usr/local/bin/rakudo-moar-2024.01-01-macos-arm64-clang/bin/../share/perl6/runtime/perl6.moarvm line 1
  in any <entry> at /usr/local/bin/rakudo-moar-2024.01-01-macos-arm64-clang/bin/../share/perl6/runtime/perl6.moarvm line 1

[107] > 

Cannot map a List using a Hash. Whaaa?! How can I call $list.map(%some_hash)?


Solution

  • This looks like a case where Raku tried to be incredibly helpful and it backfired. The relevant overloads for Raku's map are defined here.

    proto method map(|) is nodal {*}
    multi method map(Hash:D \hash) {
        X::Cannot::Map.new(
          what       => self.^name,
          using      => "a {hash.^name}",
          suggestion =>
    "Did you mean to add a stub (\{ ... \}) or did you mean to .classify?"
        ).throw;
    }
    multi method map(Iterable:D \iterable) {
        X::Cannot::Map.new(
          what       => self.^name,
          using      => "a {iterable.^name}",
          suggestion =>
    "Did a * (Whatever) get absorbed by a comma, range, series, or list repetition?
    Consider using a block if any of these are necessary for your mapping code."
        ).throw;
    }
    multi method map(|c) {
        X::Cannot::Map.new(
          what       => self.^name,
          using      => "'{c.raku.substr(2).chop}'",
          suggestion => "Did a * (Whatever) get absorbed by a list?"
        ).throw;
    }
    

    That is, all of the overloads for map except the one taking a &code object just produce specific, helpful error messages like the one you saw. Unfortunately, those overloads got picked up by some sort of (presumably) automated documentation tool and added to the docs, creating more confusion.

    It seems to be that these error-only overloads should probably be hidden from the documentation, to prevent exactly this sort of thing from tripping folks up.