如何为多位置参数应用单一签名测试,简化Raku中Measure类型运算的样板代码
Measure Operator Multi Subs in Raku Great question—those repetitive multi subs for every operator definitely add up to unnecessary boilerplate. Let’s look at a couple of clean ways to simplify this, while preserving behavior for both commutative (+, *) and non-commutative (-, /) operators.
Option 1: Merge Real-Measure/Measure-Real Signatures (Per Operator)
For each operator, you can combine the mixed Real/Measure cases into a single multi sub, using a where clause to enforce that at least one argument is a Measure. Then handle parameter order appropriately based on whether the operator is commutative.
Commutative Operators (+, *)
Since order doesn’t matter here, we can safely swap arguments to always work with a Measure first:
multi infix:<+> (Cool:D $a, Cool:D $b) where ($a ~~ Measure:D and $b ~~ Real:D) or ($a ~~ Real:D and $b ~~ Measure:D) is export { my ($measure, $real) = $a ~~ Measure:D ?? ($a, $b) !! ($b, $a); $measure.clone.add-const($real); } multi infix:<+> (Measure:D $left, Measure:D $right) is export { my ($result, $argument) = infix-prep($left, $right); $result.add($argument); }
Non-Commutative Operators (-, /)
Here we need to preserve parameter order, so we’ll use a given block to branch based on the argument types:
multi infix:<-> (Cool:D $left, Cool:D $right) where ($left ~~ Measure:D or $right ~~ Measure:D) is export { given ($left, $right) { when (Measure:D, Real:D) { my $result = $left.clone; $result.sub-const($right); } when (Real:D, Measure:D) { # Adjust logic to match your Real - Measure requirements my $temp = $right.clone.sub-const($left); $temp.negate; # Assuming you have a negate method for Measure } when (Measure:D, Measure:D) { my ($result, $argument) = infix-prep($left, $right); $result.sub($argument); } } }
This cuts down the number of multi subs per operator from 3 to 2, eliminating the duplicate Real-Measure/Measure-Real boilerplate.
Option 2: Use Macros to Generate All Operators (Full Boilerplate Elimination)
If you want to avoid repeating even this per-operator code, Raku’s macros let you generate all four operators in one go. This is perfect if you might add more operators later.
First, define macros for commutative and non-commutative operators:
# Macro for commutative operators (+, *) macro generate-commutative-op($op, $measure-method, $const-method) { quasi { multi infix:<$op> (Cool:D $a, Cool:D $b) where ($a ~~ Measure:D or $b ~~ Measure:D) is export { if $a ~~ Measure:D and $b ~~ Measure:D { my ($result, $argument) = infix-prep($a, $b); $result.$measure-method($argument); } else { my ($measure, $real) = $a ~~ Measure:D ?? ($a, $b) !! ($b, $a); $measure.clone.$const-method($real); } } } } # Macro for non-commutative operators (- , /) macro generate-non-commutative-op($op, $measure-method, $left-const-method, $negate-logic) { quasi { multi infix:<$op> (Cool:D $left, Cool:D $right) where ($left ~~ Measure:D or $right ~~ Measure:D) is export { given ($left, $right) { when (Measure:D, Real:D) { my $result = $left.clone; $result.$left-const-method($right); } when (Real:D, Measure:D) { $negate-logic; # Custom logic for Real op Measure } when (Measure:D, Measure:D) { my ($result, $argument) = infix-prep($left, $right); $result.$measure-method($argument); } } } } }
Then generate all four operators with a single line each:
# Generate + and * generate-commutative-op('+', 'add', 'add-const'); generate-commutative-op('*', 'mult', 'mult-const'); # Adjust method names to match your code # Generate - and / generate-non-commutative-op('-', 'sub', 'sub-const', { $right.clone.sub-const($left).negate }); generate-non-commutative-op('/', 'div', 'div-const', { Measure.new($left) / $right }); # Adjust logic to match your division needs
This approach turns dozens of lines of boilerplate into a handful of macro calls, making your code much easier to maintain.
Key Notes
- Type Priority: The
whereclauses ensure these multi subs only trigger when at least one argument is aMeasure, so they won’t conflict with Raku’s built-inRealoperators. - Custom Logic: Adjust the
negate-logicor method calls to match exactly how yourMeasureclass handles edge cases likeReal - MeasureorReal / Measure.
内容的提问来源于stack exchange,提问作者librasteve

