librasteve
librasteve

Reputation: 7581

Raku Ambiguous call to infix(Hyper: Dan::Series, Int)

I am writing a model Series class (kinda like the one in pandas) - and it should be both Positional and Associative.

class Series does Positional does Iterable does Associative {
    has Array $.data is required;
    has Array $.index;

    ### Construction ###

    method TWEAK {
        # sort out data-index dependencies
        $!index = gather {
            my $i = 0;
            for |$!data -> $d {
                take ( $!index[$i++] => $d )
            }
        }.Array
    }

    ### Output ### 

    method Str {
        $!index
    }   

    ### Role Support ### 

    # Positional role support
    # viz. https://docs.raku.org/type/Positional

    method of {
        Mu  
    }   
    method elems {
        $!data.elems
    }   
    method AT-POS( $p ) { 
        $!data[$p]
    }   
    method EXISTS-POS( $p ) { 
        0 <= $p < $!data.elems ?? True !! False
    }   

    # Iterable role support
    # viz. https://docs.raku.org/type/Iterable

    method iterator {
        $!data.iterator
    }   
    method flat {
        $!data.flat
    }   
    method lazy {
        $!data.lazy
    }   
    method hyper {
        $!data.hyper
    }   

    # Associative role support
    # viz. https://docs.raku.org/type/Associative

    method keyof {
        Str(Any)
    }   
    method AT-KEY( $k ) { 
        for |$!index -> $p {
            return $p.value if $p.key ~~ $k
        }   
    }   
    method EXISTS-KEY( $k ) { 
        for |$!index -> $p {
            return True if $p.key ~~ $k
        }   
    }   

    #`[ solution attempt #1 does NOT get called
    multi method infix(Hyper: Series, Int) is default {
        die "I was called"
    }
    #]
}

my $s = Series.new(data => [rand xx 5], index => [<a b c d e>]);

say ~$s;
say $s[2];
say $s<b>;

So far pretty darn cool.

I can go dd $s.hyper and get this

HyperSeq.new(configuration => HyperConfiguration.new(batch => 64, degree => 1))

BUT (there had to be a but coming), I want to be able to do hyper math on my Series' elements, something like:

say $s >>+>> 2;

But that yields:

Ambiguous call to 'infix(Hyper: Dan::Series, Int)'; these signatures all match:
  (Hyper: Associative:D \left, \right, *%_)
  (Hyper: Positional:D \left, \right, *%_)
  in block <unit> at ./synopsis-dan.raku line 63

How can I tell my class Series not to offer the Associative hyper candidate...?


Note: edited example to be a runnable MRE per @raiph's comment ... I have thus left in the minimum requirements for the 3 roles in play per docs.raku.org

Upvotes: 8

Views: 144

Answers (2)

raiph
raiph

Reputation: 32454

Take #1

First, an MRE with an emphasis on the M1:

class foo does Positional does Associative { method of {} }
sub infix:<baz> (\l,\r) { say 'baz' }
foo.new >>baz>> 42;

yields:

Ambiguous call to 'infix(Hyper: foo, Int)'; these signatures all match:
  (Hyper: Associative:D \left, \right, *%_)
  (Hyper: Positional:D \left, \right, *%_)
  in block <unit> at ./synopsis-dan.raku line 63

The error message shows it's A) a call to a method named infix with an invocant matching Hyper, and B) there are two methods that potentially match that call.

Given that there's no class Hyper in your MRE, these methods and the Hyper class must be either built-ins or internal details that are leaking out.

A search of the doc finds no such class. So Hyper is undocumented Given that the doc has fairly broad coverage these days, this suggests Hyper is an internal detail. But regardless, it looks like you can't solve your problem using official/documented features.

Hopefully this bad news is still better than none.2

Take #2

Where's the fun in letting little details like "not an official feature" stop us doing what we want to do?

There's a core.c module named Hyper.pm6 in the Rakudo source repo.

A few seconds browsing that, and clicks on its History and Blame, and I can instantly see it really is time for me to conclude this SO answer, with a recommendation for your next move.

To wit, I suggest you start another SO, using this answer as its heart (but reversing my presentation order, ie starting by mentioning Hyper, and that it's not doc'd), and namechecking Liz (per Hyper's History/Blame), with a link back to your Q here as its background. I'm pretty sure that will get you a good answer, or at least an authoritative one.

Footnotes

1 I also tried this:

class foo does Positional does Associative { method of {} }
sub postfix:<bar>(\arg) { say 'bar' }
foo.new>>bar;

but that worked (displayed bar).

2 If you didn't get to my Take #1 conclusion yourself, perhaps that was was because your MRE wasn't very M? If you did arrive at the same point (cf "solution attempt #1 does NOT get called" in your MRE) then please read and, for future SOs, take to heart, the wisdom of "Explain ... any difficulties that have prevented you from solving it yourself".

Upvotes: 2

librasteve
librasteve

Reputation: 7581

After some experimentation (and new directions to consider from the very helpful comments to this SO along the way), I think I have found a solution:

  1. drop the does Associative role from the class declaration like this:
class Series does Positional does Iterable {...}

BUT

  1. leave the Associative role support methods in the body of the class:
    # Associative role support
    # viz. https://docs.raku.org/type/Associative

    method keyof {
        Str(Any)
    }   
    method AT-KEY( $k ) { 
        for |$!index -> $p {
            return $p.value if $p.key ~~ $k
        }   
    }   
    method EXISTS-KEY( $k ) { 
        for |$!index -> $p {
            return True if $p.key ~~ $k
        }   
    }   

This gives me the Positional and Associative accessors, and functional hyper math operators:

my $s = Series.new(data => [rand xx 5], index => [<a b c d e>]);

say ~$s;        #([a => 0.6137271559776396 b => 0.7942959887386045 c => 0.5768018697817604 d => 0.8964323560788711 e => 0.025740150933493577] , dtype: Num)

say $s[2];      #0.7942959887386045
say $s<b>;      #0.5768018697817604
say $s >>+>> 2; #(2.6137271559776396 2.7942959887386047 2.5768018697817605 2.896432356078871 2.0257401509334936)

While this feels a bit thin (and probably lacks the full set of Associative functions) I am fairly confident that the basic methods will give me slimmed down access like a hash from a key capability that I seek. And it no longer creates the ambiguous call.

This solution may be cheating a bit in that I know the level of compromise that I will accept ;-).

Upvotes: 3

Related Questions