How to apply one signature test to multiple positionals
Asked Answered
A

3

11

I wrote some code in https://github.com/p6steve/raku-Physics-Measure that looks for a Measure type in each maths operation and hands off the work to non-standard methods that adjust Unit and Error aspects alongside returning the new value:

multi infix:<+> ( Measure:D $left, Real:D $right ) is export {
    my $result   = $left.clone;
    my $argument = $right;
    return $result.add-const( $argument );
}
multi infix:<+> ( Real:D $left, Measure:D $right ) is export {
    my $result   = $right.clone;
    my $argument = $left;
    return $result.add-const( $argument );
}
multi infix:<+> ( Measure:D $left, Measure:D $right ) is export {
    my ( $result, $argument ) = infix-prep( $left, $right );
    return $result.add( $argument );
}

This pattern is repeated 4 times for <[+-*/]> so it amounts to quite a lot of boilerplate; I'd like to reduce that a bit.

So, is there a more terse way to apply a single Measure|Real test in the signature to both Positionals in a way that the multi is triggered if both or one but not neither match and that the position is preserved for the intransigent operations <[-/]>?

I am not sure that getting to no multis is the most elegant - perhaps just compress the Real-Measure and Measure-Real to one?

Allison answered 29/12, 2021 at 22:31 Comment(3)
.oO ( "In the process of automating bakery operations, one of the most difficult and intransigent operations has been that of loading the transport racks" )Wildee
hammer of intransigenceAllison
Ahh. .oO( "hammer that down" = "opinion-based language"! )Wildee
O
10

There are a few ways to approach this but what I'd probably do – and a generally useful pattern – is to use a subset to create a slightly over-inclusive multi and then redispatch the case you shouldn't have included. For the example you provided, that might look a bit like:

subset RealOrMeasure where Real | Measure;
multi infix:<+> ( RealOrMeasure:D $left, RealOrMeasure:D $right )  {
    given $left, $right {
       when Real,    Real    { nextsame }
       when Real,    Measure { $right.clone.add-const($left)  }
       when Measure, Real    {  $left.clone.add-const($right) }
       when Measure, Measure { my ($result, $argument) = infix-prep $left, $right;
                               $result.add($argument)}}

}

(Note: I haven't tested this code with Measure; let me know if it doesn't work. But the general idea should be workable.)

Odelsting answered 30/12, 2021 at 0:41 Comment(5)
subset RealOrMeasure where * ~~ Real | Measure; Doesn't just subset RealOrMeasure where Real | Measure; work?Wildee
@Odelsting / @Wildee -- yes that's exactly what I had in mind thank you! One thing this raises is if there is a way to type check all Positionals in one go, something like ( ($left, $right).all ~~ RealOrMeasure:D )Allison
Note that subset types are tie-breakers, so using this approach will impact upon the way the candidates are sorted, which might lead to surprises.Gauss
( Wait till 🕛 🎆 ... Merry 2022! ) @JonathanWorthington I'm not sure what surprises you mean, and can't tell what specifics of "this approach" risk incurring surprises. Perhaps the surprises I avoided in my answer (by introducing a proto) are (some of?) the surprises you're talking about? And/or perhaps you know of / can link to an existing resource (eg a doc section or SO) that digs into what you're talking about? And/or is this appropriate food for thought? And/or perhaps you could write a new SO Q+A pair that outlines the surprises?Wildee
@Wildee For example, compare multi m(Int) { 1 }; multi m(Real) { 2 }; say m 1 (nominal type match, Int is tighter) against subset IntSub where Int; multi m(IntSub) { 1 }; multi m(Real) { 2 }; say m 1 (the subset implies a nominal type of Any, and Real is tighter). The proto approach may well avoid that, but implies cost of redispatch and won't compose if multiple modules attempt that strategy.Gauss
S
3

You can add an Int - method to your class and use signature coercion.

class Foo { 
    method Int { 110 };
} 
multi t( Int() $x ) { $x + 1 };
multi t( Real $x ) { $x.Int + 11 };

say t( 0 );
say t( 0.1 );
say t( Foo.new );
Shivery answered 30/12, 2021 at 21:35 Comment(2)
5,000 is better than 5,002Shifra
@holli - thanks for your answer, but I need to preserve the Real-ness of any values... for example $x = Measure.new(value => 27.5, units => 'm') + 13.2; say +$x; #40.7Allison
A
0

I just found this example in the raku docs:

sub f(*@a where {$_.all ~~ Int}) { say @a };

I now plan to combine this with @codesections answer...

Allison answered 3/2, 2022 at 16:1 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.