11
\$\begingroup\$

I am studying chemistry in the university, and then I try to write all the things in the textbook with Perl6 or Perl, like balancing the chemical formula or other process!

Then I encountered the problem is on perl6 custom operator. I feel I have been repeating my code and myself when i use the feature. It is hard to read and write, and what is the way to deal with such problems?

#!/usr/bin/env perl6
use v6;
#basic SI(International System of Units) type 
role MetricPrefix {
 method baseOn ( Str $base , Numeric $input ) {
 given $base {
 when 'pico' { return $input * 10**-12 }
 when 'namo' { return $input * 10**-9 }
 when 'micro' { return $input * 10**-6}
 when 'milli' { return $input * 10**-3 }
 when 'centi' { return $input * 10**-2 }
 when 'hecto' { return $input * 10**2 }
 when 'kilo' { return $input * 10**3 }
 when 'mega' { return $input * 10**6 }
 when 'giga' { return $input * 10**9 }
 when 'tera' { return $input * 10**12 }
 default { fail "you must input a metric prefix which allow pico to tera" }
 }
 }
}
class Mass does MetricPrefix {
 #basic Mass is g is different form si statda
 has $.g;
 submethod BUILD ( :$!g ) {
 }
}
class Length does MetricPrefix {
 has $.Length ;
 submethod BUILD ( :$!Length ) {
 }
}
multi postfix:<(kg)>( $input ) {
 return Mass.new( g => Mass.baseOn("kilo",$input) ) or fail "you Must input a number";
}
multi postfix:<(g)>( $input ) {
 return Mass.new( g => $input ) or fail "you Must input a number";
}
multi infix:<+>( Mass $inputOne , Mass $inputTwo ) is assoc<right> {
 return Mass.new( g => $inputOne.g + $inputTwo.g) or fail "error in there ";
}
multi infix:<->( Mass $inputOne , Mass $inputTwo ) is assoc<right> {
 return Mass.new( g => $inputOne.g - $inputTwo.g) or fail "error in there ";
}
multi infix:<*>( Mass $inputOne , Mass $inputTwo ) is assoc<right> is tighter( &infix:<+> ) is tighter( &infix:<-> ) is tighter( &infix:</>) {
 return Mass.new( g => $inputOne.g * $inputTwo.g) or fail "error in there ";
}
multi infix:</>( Mass $inputOne , Mass $inputTwo ) is assoc<right> is tighter( &infix:<+> ) is tighter( &infix:<-> ) {
 return Mass.new( g => $inputOne.g / $inputTwo.g) or fail "error in there ";
}
#the meterLeng
multi postfix:<(km)>( $input ) {
 return Length.new( Length => Length.baseOn("kilo",$input) ) or fail "you Must input a number";
}
multi postfix:<(m)>( $input ) {
 return Length.new( Length => $input ) or fail "you Must input a number";
}
multi infix:<+>( Length $inputOne , Length $inputTwo ) is assoc<right> {
 return Length.new( Length => $inputOne.Length + $inputTwo.Length) or fail "error in there ";
}
multi infix:<->( Length $inputOne , Length $inputTwo ) is assoc<right> {
 return Length.new( Length => $inputOne.Length - $inputTwo.Length) or fail "error in there ";
}
multi infix:<*>( Length $inputOne , Length $inputTwo ) is assoc<right> is tighter( &infix:<+> ) is tighter( &infix:<-> ) is tighter( &infix:</>) {
 return Length.new( Length => $inputOne.Length * $inputTwo.Length) or fail "error in there ";
}
multi infix:</>( Length $inputOne , Length $inputTwo ) is assoc<right> is tighter( &infix:<+> ) is tighter( &infix:<-> ) {
 return Length.new( Length => $inputOne.Length / $inputTwo.Length) or fail "error in there ";
}
#just a test
say 10(kg) + 1(g);
say 10(m) + 1(m);
rolfl
98.1k17 gold badges219 silver badges419 bronze badges
asked Mar 28, 2017 at 16:32
\$\endgroup\$
1
  • 2
    \$\begingroup\$ Welcome to Code Review! I hope you get great answers. \$\endgroup\$ Commented Mar 28, 2017 at 16:46

3 Answers 3

4
\$\begingroup\$

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 an Area etc, and Area * Length getting Volume

answered Sep 10, 2019 at 19:49
\$\endgroup\$
2
  • \$\begingroup\$ Would it make sense to replace "μ" with "u"? If this is intended as a module, it seems more usable to use "u" than copy-pasting "μ" every time. \$\endgroup\$ Commented Sep 11, 2019 at 4:34
  • \$\begingroup\$ @AlexF I guess I forget not everyone has Greek, etc, keyboards on speed dial haha. If we're adding 20+ operators, probably won't hurt to add one more to have both μg and ug as synonyms. That way you could say say 45ug and it would still output 45μg \$\endgroup\$ Commented Sep 11, 2019 at 4:41
3
\$\begingroup\$

I have been through your code and I could not find a clear answer. I have tried another approach to sum a set of weights in case it is useful:

#!/usr/bin/env perl6
use v6;
class Mass {
 has Int $.value;
 has Str $.prefix;
 method convert_to_grams() {
 # From: https://en.wikipedia.org/wiki/Metric_prefix
 my %prefix = Yg => 24, Zg => 21, Eg => 18, Pg => 15, Tg => 12, Gg => 9, Mg => 6, kg => 3, hg => 2, dag => 1, g => 0,
 dg => −1, cg => −2, mg => −3, μg => −6, ng => −9, pg => −12, fg => −15, ag => −18, zg => −21, yg => −24; 
 self.value * 10** %prefix{self.prefix};
 }
}
class Weights {
 has @.items;
 method sum() {
 my $total = 0;
 for self.items -> $item {
 $item ~~ /
 $<value> = (<digit>+)
 $<prefix> = (<alpha>+)
 /;
 my $mass = Mass.new( value => +$<value>.Str, prefix => $<prefix>.Str );
 $total += $mass.convert_to_grams();
 }
 return $total ~ "g";
 }
}
my @list = < 10kg 100g 6μg >;
my $weights = Weights.new( items => @list );
say $weights.sum();
answered May 18, 2017 at 12:30
\$\endgroup\$
2
  • 1
    \$\begingroup\$ Thanks for your answer. In fact, I find more cleanly way to solve the problem by use enum in perlmonk . \$\endgroup\$ Commented May 20, 2017 at 4:34
  • 1
    \$\begingroup\$ Thanks for pointing at the solution. I was not aware of 'enum'. More to learn! Thanks! \$\endgroup\$ Commented May 21, 2017 at 0:57
1
\$\begingroup\$

It sounds strange that your usual arithmetic operators are assoc<right>. For example I would expect that 9kg - 3kg - 1kg would result in 5kg, but your code looks like it would produce 9kg - (3kg - 1kg), which results in 7kg.

There's a typo: namo should be nano.

answered Sep 10, 2019 at 22:10
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.