Most trusting
I was looking for a neat way to specify units when working with numbers. When doing dimensional analysis, many physicists like to put units into square brackets to create an additional namespace. We can do the same.
use v6.d;
class Unit { ... }
class SiInt is Int {
trusts GLOBAL;
trusts Unit;
has Unit $!unit;
method !unit { return-rw $!unit }
method new(CORE::Int $new) { nextsame }
method Str { self.Int::Str ~ $!unit.suffix }
method ACCEPTS(Unit:U $u) { $!unit === $u }
}
class Unit {
our $.suffix = '';
our $.long-name = "unit-less";
method ACCEPTS(SiInt $si) { $si!SiInt::unit === self }
}
class m is Unit { our $.suffix = 'm'; our $.long-name = 'Meter'; }
multi sub postcircumfix:<[ ]>(SiInt $obj, Unit:U $unit) {
$obj!SiInt::unit === Unit ?? ($obj!SiInt::unit = $unit)
!! fail(‘Sorry, units can only be set, not changed.’);
$obj
}
multi sub postcircumfix:<[ ]>(Int $value, Unit:U $unit) { SiInt.new($value)[$unit] }
constant Int = SiInt; # intentional shadowing of CORE::Int
my $i = 42[m];
put [$i, $i.^name]; # 42m SiInt
my Int() $a = 1;
put [$a, $a.^name]; # 1 SiInt
class s is Unit { our $.suffix = 's'; our $.long-name = 'Seconds'; }
multi sub infix:<+>(SiInt $l, SiInt $r) {
$l!SiInt::unit === Unit ?? callsame()[$r!SiInt::unit]
!! $r!SiInt::unit === Unit ?? callsame()[$l!SiInt::unit]
!! $l!SiInt::unit === $r!SiInt::unit ?? nextsame()
!! fail(„Unit mismatch between $l and $r“)
}
my $s = 60[s];
say $i + $a; # 43m
say $s + $i; # Unit mismatch between 60s and 42m
The idea is to have a numerical type that is by default unit-less. A unit can be added (but not changed) with square bracket postcircumfix. Since I add type-objects for each unit, I don’t have to mess around with strings and can multi-dispatch if needed. Since I want direct access to the unit, I tell the class to trust the package the operators are defined in. (This could be a module, of course.) What happens to be an ENODOC.
I have to use a forward declaration to get ACCEPTS
to get hold of $!unit
. Subsequently, multi-dispatch works just fine.
multi sub fancy(Int $value where m) { #`[fancy calculation goes here] }
multi sub fancy(Int) { fail ‘Can only be fancy with Unit "m".’ }
fancy($i);
Since SiInt
is just an Int
all built-ins will work, so long the unit is restored after using them. Being able to trust operators allows them to access the entire class, without having to cheat with use nqp;
.
Because Raku treats types as values, I can calculate a compound unit.
class Meters-per-Second is Unit { our $.suffix = 'm/s'; our $.long-name = 'Meters per Second'; }
multi sub infix:</>(m, s) { Meters-per-Second }
sub speed($d where m, $t where s) { ($d / $t).Int.[m/s] }
my Int $fast = speed(500[m], 1[s]);
say $fast; # 500m/s
I’m quite pleased with being able to extend the type-system so easily without having to invent a complete new DSL. This aids composability greatly.
Nice post – it’s funny how raku is such a great fit for “annotating” numbers with (e.g.) SI units. You may want to check out Physics::Unit and Physics::Measure …. which aim to do this with raw postfixen like “`my $d = 2km;“` or “`my $t = 3600s;“` ;-)
I’m reading all Raku code I can get hold of.
… and, like most code I write, particularly in raku, I feel that your rather MOPpy oriented code has a lot of good lessons, so I will definitely steal some/all of your concepts in the next rewrite!