sub stuff-with-lengths {
use SI-Operators(:length);length;
say 25mm + 78km # 78000.025m
}
Note that multiplying, etc, would need you to implement more math operator subs. But could also lead to interesting things like a Length * Length
generating aan Area
etc, and Area * Length
getting Volume
etc.
sub stuff-with-lengths {
use SI-Operators(:length);
say 25mm + 78km # 78000.025m
}
Note that multiplying, etc, would need you to implement more math operator subs. But could also lead to interesting things like a Length * Length
generating a Volume
etc.
sub stuff-with-lengths {
use SI-Operators:length;
say 25mm + 78km # 78000.025m
}
Note that multiplying, etc, would need you to implement more math operator subs. But could also lead to interesting things like a Length * Length
generating an Area
etc, and Area * Length
getting Volume
This is an interesting use case of postfixes. Here's how I would go about it. Unfortunately, because postfixes can't take regexen, you need to specify each one individually, but it's not horrible, especially if you throw things into a module.
Let's first create a postfix operator for each value type:
multi postfix:<kg> (Numeric() $m) { Mass.new: $m, 'k' }
multi postfix:<g> (Numeric() $m) { Mass.new: $m }
multi postfix:<mg> (Numeric() $m) { Mass.new: $m, 'm' }
Basically, we're saying we want to attach g
onto something. We'd like it to be a number type, so we make have it only act on Numeric
types, but if someone passes us a string, the ()
tells it to coerce it for us. And then we make a Mass
object from those the value and the prefix.
Now let's take a look at the Mass
class. All we really need to have in it is a weight and probably store its prefix so if we create a kg
if we want to display it we can keep it in kg
and not switch to mg
or something. We'll store things in grams, so on creation we use the prefix to shrink/expand the value.
class Mass {
has $.grams;
has $.prefix;
method new ($value, $prefix) {
self.bless:
:grams($value * si-prefix{$prefix}),
:$prefix
}
}
The si-prefix
is just a map of values:
my constant \si-prefix := Map.new( 'k', 1000, '', 1, 'm', 0.001 );
By making it independent of the gram, we can use it later for a length / etc class. For that reason, I'd define it outside of the class so that it can be used by others easily. Believe it or not, it is possible to have an empty string as a key. Kind of useful in our case.
At this point, we can start to use our values:
say 500g; # --> Weight.new(grams => 500, prefix => "")
say 2kg; # --> Weight.new(grams => 2000, prefix => "kg")
Okay, not the most useful output, but at least we know everything is working. Let's make a Str
and gist
method that makes more sense:
method Str {
$!grams / si-prefix{$!prefix}
~ $!prefix
~ 'g'
}
method gist { self.Str }
Now all we really have to do is handle the addition:
multi infix:<+> (Mass \a, Mass \b) { Weight.new: a.grams + b.grams }
multi infix:<-> (Mass \a, Mass \b) { Weight.new: a.grams - b.grams }
This works great, but does automatically convert everything back to being a g
unit even if both were kg
, etc. At this point, it's a bit of taste. You could decide that the first or second unit has precedence, or implement some more complex logic (larger or smaller, or the one that results in no more than three decimal places or no more than three whole units, etc). I'll leave that for you to implement.
As I said, this would work great as a module. You can do that like this:
unit module SI-Operators;
my constant \si-prefix := Map.new(
'Y', 10 ** 24, 'y', 10 ** -24,
'Z', 10 ** 21, 'z', 10 ** -21,
'E', 10 ** 18, 'a', 10 ** -18,
'P', 10 ** 15, 'f', 10 ** -15,
'T', 10 ** 12, 'p', 10 ** -12,
'G', 10 ** 9, 'n', 10 ** -9,
'M', 10 ** 6, 'μ', 10 ** -6,
'k', 10 ** 3, 'm', 10 ** -3,
'h', 10 ** 2, 'c', 10 ** -2,
'da', 10 ** 1, 'd', 10 ** -1,
'', 1
);
# MASSES
class Mass is export (:mass) {
has $.grams;
has $.prefix;
method new ($number, $prefix = '') {
self.bless:
:grams($number * si-prefix{$prefix}),
:$prefix
}
method Str {
$!grams / si-prefix{$!prefix}
~ $!prefix
~ 'g'
}
method gist { self.Str }
}
multi infix:<+> (Mass \a, Mass \b) is export (:mass) { Mass.new: a.grams + b.grams }
multi infix:<-> (Mass \a, Mass \b) is export (:mass) { Mass.new: a.grams - b.grams }
multi postfix:<Yg> (Numeric() $m) is export (:mass) { Mass.new: $m, 'Y' }
multi postfix:<Zg> (Numeric() $m) is export (:mass) { Mass.new: $m, 'Z' }
multi postfix:<Eg> (Numeric() $m) is export (:mass) { Mass.new: $m, 'E' }
multi postfix:<Pg> (Numeric() $m) is export (:mass) { Mass.new: $m, 'P' }
multi postfix:<Tg> (Numeric() $m) is export (:mass) { Mass.new: $m, 'T' }
multi postfix:<Gg> (Numeric() $m) is export (:mass) { Mass.new: $m, 'G' }
multi postfix:<Mg> (Numeric() $m) is export (:mass) { Mass.new: $m, 'M' }
multi postfix:<kg> (Numeric() $m) is export (:mass) { Mass.new: $m, 'k' }
multi postfix:<hg> (Numeric() $m) is export (:mass) { Mass.new: $m, 'h' }
multi postfix:<dag> (Numeric() $m) is export (:mass) { Mass.new: $m, 'da' }
multi postfix:<g> (Numeric() $m) is export (:mass) { Mass.new: $m }
multi postfix:<dg> (Numeric() $m) is export (:mass) { Mass.new: $m, 'd' }
multi postfix:<cg> (Numeric() $m) is export (:mass) { Mass.new: $m, 'c' }
multi postfix:<mg> (Numeric() $m) is export (:mass) { Mass.new: $m, 'm' }
multi postfix:<μg> (Numeric() $m) is export (:mass) { Mass.new: $m, 'μ' }
multi postfix:<ng> (Numeric() $m) is export (:mass) { Mass.new: $m, 'n' }
multi postfix:<pg> (Numeric() $m) is export (:mass) { Mass.new: $m, 'p' }
multi postfix:<fg> (Numeric() $m) is export (:mass) { Mass.new: $m, 'f' }
multi postfix:<ag> (Numeric() $m) is export (:mass) { Mass.new: $m, 'a' }
multi postfix:<zg> (Numeric() $m) is export (:mass) { Mass.new: $m, 'z' }
multi postfix:<yg> (Numeric() $m) is export (:mass) { Mass.new: $m, 'y' }
# LENGTH
class Length is export (:length) {
has $.meters;
has $.prefix;
method new ($number, $prefix = '') {
self.bless:
:meters($number * si-prefix{$prefix}),
:$prefix
}
method Str {
$!meters / si-prefix{$!prefix}
~ $!prefix
~ 'm'
}
method gist { self.Str }
}
multi infix:<+> (Length \a, Length \b) is export (:length) { Length.new: a.meters + b.meters }
multi infix:<-> (Length \a, Length \b) is export (:length) { Length.new: a.meters - b.meters }
multi postfix:<Ym> (Numeric() $l) is export (:mass) { Length.new: $l, 'Y' }
multi postfix:<Zm> (Numeric() $l) is export (:mass) { Length.new: $l, 'Z' }
multi postfix:<Em> (Numeric() $l) is export (:mass) { Length.new: $l, 'E' }
multi postfix:<Pm> (Numeric() $l) is export (:mass) { Length.new: $l, 'P' }
multi postfix:<Tm> (Numeric() $l) is export (:mass) { Length.new: $l, 'T' }
multi postfix:<Gm> (Numeric() $l) is export (:mass) { Length.new: $l, 'G' }
multi postfix:<Mm> (Numeric() $l) is export (:mass) { Length.new: $l, 'M' }
multi postfix:<km> (Numeric() $l) is export (:mass) { Length.new: $l, 'k' }
multi postfix:<hm> (Numeric() $l) is export (:mass) { Length.new: $l, 'h' }
multi postfix:<dam> (Numeric() $l) is export (:mass) { Length.new: $l, 'da' }
multi postfix:<m> (Numeric() $l) is export (:mass) { Length.new: $l }
multi postfix:<dm> (Numeric() $l) is export (:mass) { Length.new: $l, 'd' }
multi postfix:<cm> (Numeric() $l) is export (:mass) { Length.new: $l, 'c' }
multi postfix:<mm> (Numeric() $l) is export (:mass) { Length.new: $l, 'm' }
multi postfix:<μm> (Numeric() $l) is export (:mass) { Length.new: $l, 'μ' }
multi postfix:<nm> (Numeric() $l) is export (:mass) { Length.new: $l, 'n' }
multi postfix:<pm> (Numeric() $l) is export (:mass) { Length.new: $l, 'p' }
multi postfix:<fm> (Numeric() $l) is export (:mass) { Length.new: $l, 'f' }
multi postfix:<am> (Numeric() $l) is export (:mass) { Length.new: $l, 'a' }
multi postfix:<zm> (Numeric() $l) is export (:mass) { Length.new: $l, 'z' }
multi postfix:<ym> (Numeric() $l) is export (:mass) { Length.new: $l, 'y' }
What's great about doing it as a module is you can only import what you need. For example, if in most of your program you're not using SI units, there's no reason to polute the operator namespace with lots of postfixes. But when you do:
sub stuff-with-lengths {
use SI-Operators(:length);
say 25mm + 78km # 78000.025m
}
Note that multiplying, etc, would need you to implement more math operator subs. But could also lead to interesting things like a Length * Length
generating a Volume
etc.