5

Got a weird situation I think might be a bug. Not sure. But I have this:


class Y {
    has Int $.vol;
    has Str $.quant;
    has Str $.abbr;
    submethod BUILD(
                    Str      :$!abbr,
                    Int      :$!vol,
                    ) { }
}

class X is Y {
    multi method new(Int:D :$vol) {
        self.bless(
                :$vol,
                :abbr('C'),
                );
    }

    multi method new(Str:D :$quant) {
        self.bless(
                :$quant,
                :abbr('C'),
                );
    }

}

my $x = X.new(vol => 7);
say $x;

# OUTPUT: X.new(vol => 7, abbr => "C")

This works as expected. The abbr attribute is populated. However, in the code below, I can't get the abbr to populate:

#!/usr/bin/env raku
use v6.d;
use Lingua::EN::Numbers;
constant Nᴀ = 602214076000000000000000;

class Mass-t { ... }
class Mass-kg { ... }
class Mass-g { ... }
class Mass-mg { ... }
class Mass-ug { ... }
class Mass-mcg { ... }
class Mass-oz { ... }
class Mass-lb { ... }
class Mass-c { ... }
class Volume-m { ... }
class Volume-cm { ... }
class Volume-mm { ... }
class Length-cm { ... }
class Length-m { ... }
class Length-mm { ... }
class Quantity-mol { ... }
class Quantity-dz { ... }
class Substance-c { ... }
class Substance-air { ... }
class Volume { ... }

END {
    my $v = Quantity-mol.new(1.0);
    my $c = Substance-c.new(quant => $v);
    say $c;
    say 'done with program';
    exit;
}

class Quantity {
    has Str $.name;
    has Str $.abbr is rw;
    has Rat $.value;
    has Rat $.base_value;

    submethod BUILD(Rat :$!value,
                    Str      :$!name,
                    Str      :$!abbr,
                    Rat      :$!base_value,
    ) { }

    method get_value() {
        return $!value ~ ' ' ~ $!abbr;
    }

    method count(Bool:D $comma = True) {
        return comma $!value * $!base_value if $comma;
        return $!value * $!base_value;
    }

    method to(Str:D $abbr-in) {
        my $class = split('-', self.^name)[0];
        my $abbr = $abbr-in.lc;
        my $to = ::("$class-$abbr").new();
        self!convert($to);
    }

    method !convert(Quantity:D $to) {
        my $result = comma (self.count(False) / $to.base_value);
        return $result ~ ' ' ~ $to.abbr;
    }
}

class Quantity-mol is Quantity {
    method new(Rat:D() $value = 1.0) {
        self.bless(
                :name('mole'),
                :abbr('㏖'),
                :base_value(Rat.new(Nᴀ, 1)),
                :$value,
                );
    }
}

class Quantity-dz is Quantity {
    method new(Rat:D() $value = 1.0) {
        self.bless(
                :base_value(12.0),
                :name('dozen'),
                :abbr('dz'),
                :$value,
        );
    }
}

class Measure {
    has Str $!name;
    has Str $.abbr is rw;
    has Rat $.value;
    has Rat $.base_value;
    has Rat $.imp_base_value;

    submethod BUILD(Rat :$!value,
                    Str      :$!name,
                    Str      :$!abbr,
                    Rat      :$!base_value,
                    Rat      :$!imp_base_value,
                    ) { }

    method to(Str:D $abbr-in) {
        my $class = split('-', self.^name)[0];
        my $abbr = $abbr-in.lc;
        my $to = ::("$class-$abbr").new();
        self!convert($to)
    }

    method !convert(Measure:D $to) {
        my $imp_conv = $.imp_base_value && $to.imp_base_value;
        my $base_value = $imp_conv ?? $.imp_base_value !! $.base_value;
        my $to_base_value = $imp_conv ?? $to.imp_base_value !! $to.base_value;
        my $conversion = ($.value * $base_value) / $to_base_value;
        my $num = $conversion > 1 ?? comma ($conversion) !! $conversion < 1/10000 ?? (sprintf "%.5e", $conversion) !! $conversion;

        # do pretty scientific notation
        if $conversion < 1 / 10000 {
            my $exp = $num ~~ / '-' 0* <(\d+)>$/;
            $exp .= trans( '0123456789' => '⁰¹²³⁴⁵⁶⁷⁸⁹' );
            $num .= subst(/e\-\d+/, "\x[00D7]10\x[207B]$exp");
        }
        return $num ~ ' ' ~ $to.abbr;
    }

    method !count {

    }
}

class Mass is Measure { }
class Length is Measure { }
class Substance {
    has Quantity-mol $.quant;
    has Volume $.vol;
    has Str $.abbr is rw;

    submethod BUILD(Quantity-mol :$!quant,
                    Volume   :$!vol,
                    Str      :$!abbr,
    ) {  }

    method volume() {
        return $.vol.value ~ ' ' ~ $.vol.abbr;
    }

    method moles() {
        return $.quant.value ~ ' ' ~ $.quant.abbr ~ ' ' ~ $.abbr;
    }

}

class Substance-co2 is Substance {
    method new(Rat:D() $value = 1.0) {
        self.bless(
            :abbr('cm'),
            :$value,
        );
    }

}

class Substance-c is Substance {
    multi method new(Volume:D $vol) {
        self.bless(
            :$vol,
            :abbr('C'),
        );
    }

    multi method new(Quantity-mol:D $quant) {
        self.bless(
            :$quant,
            :abbr('C'),
        );
    }

}

class Substance-air is Substance {

}

class Length-cm is Length {
    method new(Rat:D() $value = 1.0) {
        self.bless(
            :abbr('cm'),
            :base_value(1/100),
            :$value,
        );
    }
}

class Length-m is Length {
    method new(Rat:D() $value = 1.0) {
        self.bless(
            :abbr('m'),
            :base_value(1.0),
            :$value,
        );
    }
}

class Length-mm is Length {
    method new(Rat:D() $value = 1.0) {
        self.bless(
            :abbr('mm'),
            :base_value(1/1000),
            :$value,
        );
    }
}

class Volume is Measure {
    has Str $.abbr;
    has Rat $.base_value;
    has Rat $.value;
}


class Volume-m is Volume {
    method new(Rat:D() $value = 1.0) {
        self.bless(
            :abbr('m³'),
            :base_value(1.0),
            :$value,
            );
    }
}


class Volume-cm is Volume {
    method new(Rat:D() $value = 1.0) {
        self.bless(
            :abbr('cm³'),
            :base_value(1 / 1_000_000),
            :$value,
        );
    }
}

class Volume-l is Volume {
    method new(Rat:D() $value = 1.0) {
        self.bless(
            :abbr('l'),
            :base_value(1 / 1_000),
            :$value,
        );
    }
}

class Volume-ml is Volume {
    method new(Rat:D() $value = 1.0) {
        self.bless(
                :abbr('ml'),
                :base_value(1 / 1_000_000),
                :$value,
                );
    }
}

class Volume-mm is Volume {
    method new(Rat:D() $value = 1.0) {
        self.bless(
            :abbr('mm³'),
            :base_value(1 / 1_000_000_000),
            :$value,
        );
    }
}

class Density {
    has Mass $.mass;
    has Volume $.volume;

    method calculate() {
        return $.mass / $.volume;
    }
}

class Density-water {
    method new(Rat:D()) {
        self.bless(
            :mass(Mass-kg.new()),
            :volume(Volume-l.new()),
        );
    }

}

class Mass-c is Mass {
    method new(Rat:D() $value = 1.0) {
        self.bless(
                :abbr('g/mol'),
                :base_value(12.0107),
                :$value,
                );
    }
}

class Mass-air is Mass {
    method new(Rat:D() $value = 1.0) {
        self.bless(
                :abbr('g/mol'),
                :base_value(28.9647),
                :$value,
                );
    }
}

class Mass-Da is Mass {
    method new(Rat:D() $value = 1.0) {
        self.bless(
            :abbr('Da'),
            :base_value(Rat.new(1, 602214075789225073400000)),
            :$value,
        );
    }
}

class Mass-kg is Mass {
    method new(Rat:D() $value = 1.0) {
        self.bless(
            :abbr('kg'),
            :base_value(1000.0),
            :$value,
        );
    }
}

class Mass-oz is Mass {
    method new(Rat:D() $value = 1.0) {
        self.bless(
            :abbr('oz'),
            :base_value(28.3495231),
            :imp_base_value(1/16),
            :$value,
        );
    }
}

class Mass-lb is Mass {
    method new(Rat:D() $value = 1.0) {
        self.bless(
            :abbr('lb'),
            :base_value(453.59237),
            :imp_base_value(1.0),
            :$value,
        );
    }
}
# lb aliases
class Mass-g is Mass {
    method new(Rat:D() $value = 1.0) {
        self.bless(
            :abbr('g'),
            :base_value(1.0),
            :$value,
        );
    }
}

class Mass-mg is Mass {
    method new(Rat:D() $value = 1.0) {
        self.bless(
            :abbr('mg'),
            :base_value(1/1000),
            :$value,
        );
    }
}

class Mass-ug is Mass {
    method new(Rat:D() $value = 1.0) {
        self.bless(
            :abbr('µg'),
            :base_value(1/1_000_000),
            :$value,
        );
    }
}
class Mass-mcg is Mass-ug {
    method TWEAK { $.abbr = "mcg"  }
}

class Mass-mt is Mass {
    method new(Rat:D() $value = 1.0) {
        self.bless(
            :abbr('mt'),
            :base_value(1_000_000.0),
            :$value,
        );
    }
}

class Mass-gt is Mass {
    method new(Rat:D() $value = 1.0) {
        self.bless(
            :abbr('mt'),
            :base_value(1_000_000_000_000.0),
            :$value,
        );
    }
}

class Mass-t is Mass {
    method new(Rat:D() $value = 1.0) {
        self.bless(
                :abbr('t'),
                :base_value(907184.74),
                :$value,
                );
    }
}

This outputs:

Substance-c.new(quant => Quantity-mol.new(name => "mole", abbr => "㏖", value => 1.0, base_value => 602214076000000000000000.0), vol => Volume, abbr => Str)

Note here the abbr attribute never gets set. It's just says Str (be sure to scroll all the way to the right to see this). I've been staring at this for a long time and cannot figure it out what's going on.

Elizabeth Mattijsen
  • 25,654
  • 3
  • 75
  • 105
StevieD
  • 6,925
  • 2
  • 25
  • 45

1 Answers1

6

To get answers on SO, it would help if you would golf your code to be as small as possible.

Anyways, the problem is really in the way that you try to create the Substance object.

my $c = Substance-c.new(quant => $v);

You created a new candidate in Substance-c with a positional:

    multi method new(Quantity-mol:D $quant) {
        self.bless(
            :$quant,
            :abbr('C'),
        );
    }

So if you change the call to:

my $c = Substance-c.new($v);

you are in business.

So why doesn't this create an error? That's because the way you called it, uses the default new (provided by the system), which only takes named arguments. So it was not calling any of the new methods you provided.

One further note: why all the BUILD methods? None of them are needed in your example, as far as I can see.

Elizabeth Mattijsen
  • 25,654
  • 3
  • 75
  • 105
  • 1
    Thanks. Can't believe I missed that. Was using the BUILD methods because originally the attributes in the parent classes were using the `!` twigil. And apparently the only way to set those is with the BUILD method. At some point, I will go through and refactor this code and make it more concise. I'm still trying to wrap my head around when to use which twigil. – StevieD Jun 07 '22 at 12:21
  • 2
    @StevieDS generally I use the `.` twigil. If you use the `!` twigil a lot then that indicates to me that your api needs a rethink. – Brad Gilbert Jun 07 '22 at 13:27
  • 1
    @BradGilbert thanks. Yeah, for this project I decided to use what was most restrictive just so I could learn from practical experience what the `!` might be good for. So far, it's just created mostly headaches. My understanding is now that it forces you to set up a method to access the value of the attribute. I'm not clear why that's useful. – StevieD Jun 07 '22 at 14:42
  • 2
    Note that you can make a private attribute being processed with "is built": `has $!foo is built`, which would allow you to set the `$!foo` attribute with `.new` and `.bless`, but which would not generate an accessor. And conversely: `has $.bar is built(False)` will **not** allow you to set it with `.new` and `.bless`, but *would* create an accessor for the attribute. – Elizabeth Mattijsen Jun 07 '22 at 14:48
  • 3
    Ah, had forgotten about `is built`. Yeah, I definitely need to re-review the docs. It's been many weeks since I went through them in any detail. I think part of the problem is the OO docs are spread out over several different pages and cluttered up with a lot of detailed examples making it hard to pull out a cohesive big picture. A big motivation for me writing this module was to get more hands on experience with Raku OO and try to figure out the big picture. – StevieD Jun 07 '22 at 15:00
  • 2
    @StevieD private attributes tend to be more useful for caching values or for internal bookkeeping that you don't want to expose. – Brad Gilbert Jun 09 '22 at 13:28
  • 2
    @StevieD The way docs about OOP are spread out definitely makes the experience LTA, especially when you have to go back and re-read them. I wish there was a canonical single page for any Raku OOP information and question. – uzluisf Jun 14 '22 at 14:01