首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >Perl 6和“多方法新”

Perl 6和“多方法新”
EN

Stack Overflow用户
提问于 2018-06-10 21:46:31
回答 2查看 247关注 0票数 8

我有一个封装Price的类Int。我还希望它有NumStr的构造函数。我认为我可以通过使Price::new成为一个具有各种类型约束的多个方法来做到这一点,但这不是我所期望的行为。看起来,Price.new完全跳过了构造函数,直接进入了BUILD,绕过了转换逻辑。

通过查看其他Perl 6代码,我知道使用multi method new是可以接受的。但是,我还没有找到一个具有不同类型约束的多态构造函数的示例。如何重写这段代码以强制它在构造函数中使用转换逻辑?

lib/Price.pm6

代码语言:javascript
复制
#!/usr/bin/env perl6 -w

use v6;

unit class Price:ver<0.0.1>;

class X::Price::PriceInvalid is Exception {
    has $.price;

    method message() {
        return "Price $!price not valid"
    }
}

# Price is stored in cents USD
has $.price;

multi method new(Int $price) {
    say "Int constructor";
    return self.bless(:$price);
}

multi method new(Num $price) {
    say "Num constructor";
    return self.new(Int($price * 100));
}

multi method new(Str $price) {
    say "String constructor";
    $price .= trans(/<-[0..9.]>/ => '');
    unless ($price ~~ m/\.\d**2$/) {
        die(X::Price::PriceInvalid(:$price));
    }
    return self.new(Num($price));
}

submethod BUILD(:$!price) { say "Low-level BUILD constructor" }

method toString() {
    return sprintf("%.2f", ($!price/100));
}

t/

代码语言:javascript
复制
#!/usr/bin/env perl6 -w

use v6;
use Test;

use-ok 'Price', 'Module loads';
use Price;

# test constructor with Int
my Int $priceInt = 12345;
my $priceIntObj = Price.new(price => $priceInt);
is $priceIntObj.toString(), '123.45',
    'Price from Int serializes correctly';

# test constructor with Num
my $priceNum = Num.new(123.45);
my $priceNumObj = Price.new(price => $priceNum);
is $priceNumObj.toString(), '123.45',
    'Price from Num serializes correctly';

# test constructor with Num (w/ extra precision)
my $priceNumExtra = 123.4567890;
my $priceNumExtraObj = Price.new(price => $priceNumExtra);
is $priceNumExtraObj.toString(), '123.45',
    'Price from Num with extra precision serializes correctly';

# test constructor with Str
my $priceStr = '$123.4567890';
my $priceStrObj = Price.new(price => $priceStr);
is $priceStrObj.toString(), '123.45',
    'Price from Str serializes correctly';

# test constructor with invalid Str that doesn't parse
my $priceStrInvalid = 'monkey';
throws-like { my $priceStrInvalidObj = Price.new(price => $priceStrInvalid) }, X::Price::PriceInvalid,
    'Invalid string does not parse';

done-testing;

PERL6LIB=lib/ perl6 t/price.t输出

代码语言:javascript
复制
ok 1 - Module loads
Low-level BUILD constructor
ok 2 - Price from Int serializes correctly
Low-level BUILD constructor
not ok 3 - Price from Num serializes correctly
# Failed test 'Price from Num serializes correctly'
# at t/price.t line 18
# expected: '123.45'
#      got: '1.23'
Low-level BUILD constructor
not ok 4 - Price from Num with extra precision serializes correctly
# Failed test 'Price from Num with extra precision serializes correctly'
# at t/price.t line 24
# expected: '123.45'
#      got: '1.23'
Low-level BUILD constructor
Cannot convert string to number: base-10 number must begin with valid digits or '.' in '⏏\$123.4567890' (indicated by ⏏)
  in method toString at lib/Price.pm6 (Price) line 39
  in block <unit> at t/price.t line 30
EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2018-06-11 00:27:42

您编写的所有new多方法都采用一个位置参数。

代码语言:javascript
复制
:( Int $ )
:( Num $ )
:( Str $ )

不过,您正在使用命名参数调用new。

代码语言:javascript
复制
:( :price($) )

问题是,由于您没有编写一个可以接受的new,所以它使用了Mu提供的默认Mu

如果不允许内置的new,可以编写一个proto方法来阻止它搜索继承链。

代码语言:javascript
复制
proto method new (|) {*}

如果您愿意,也可以使用它来确保所有潜在的子类也遵循关于有一个位置参数的规则。

代码语言:javascript
复制
proto method new ($) {*}

如果要使用命名参数,请使用它们。

代码语言:javascript
复制
multi method new (Int :$price!){…}

您可能希望不使用new,而是使用multi BUILD

代码语言:javascript
复制
multi submethod BUILD (Int :$!price!) {
    say "Int constructor";
}

multi submethod BUILD (Num :$price!) {
    say "Num constructor";
    $!price = Int($price * 100); 
}

multi submethod BUILD (Str :$price!) {
    say "String constructor";
    $price .= trans(/<-[0..9.]>/ => '');
    unless ($price ~~ m/\.\d**2$/) {
        die(X::Price::PriceInvalid(:$price));
    }
    $!price = Int($price * 100);
}

实际上,我总是把输入乘以100,这样1就和"1"1/11e0一样。

我也会把输出除以100得到一只老鼠。

代码语言:javascript
复制
unit class Price:ver<0.0.1>;

class X::Price::PriceInvalid is Exception {
    has $.price;

    method message() {
        return "Price $!price not valid"
    }
}

# Price is stored in cents USD
has Int $.price is required;

method price () {
    $!price / 100; # return a Rat
}

# Real is all Numeric values except Complex
multi submethod BUILD ( Real :$price ){
    $!price = Int($price * 100);
}

multi submethod BUILD ( Str :$price ){
    $price .= trans(/<-[0..9.]>/ => '');
    unless ($price ~~ m/\.\d**2$/) {
        X::Price::PriceInvalid(:$price).throw;
    }
    $!price = Int($price * 100);
}

method Str() {
    return sprintf("%.2f", ($!price/100));
}
票数 9
EN

Stack Overflow用户

发布于 2018-06-11 00:23:10

new方法被声明为接受位置参数:

代码语言:javascript
复制
multi method new(Int $price) {
    say "Int constructor";
    return self.bless(:$price);
}

但是被调用为Price.new(price => $priceInt),它传递一个命名的参数。因此,因为所有希望使用额外位置参数的multi候选人都不适用。

最直接的解决方法是将构造函数调用改为Price.new($priceInt)

另一种选择是将new方法编写为multi method new(Int :$price) { ... },注意到return self.new(Int($price * 100));需要成为return self.new(price => Int($price * 100));才能适应这种变化。

关于代码的其他几个分类说明可能会有所帮助:

  • 通常会重写new方法以将接口更改为构造(例如接受位置参数而不是命名参数),而BUILDTWEAK则用于控制如何将值映射到属性。如果您选择让new方法接受命名参数,那么处理BUILD内部的强制登录可能也更好。
  • 在Perl 6中,Num是一个浮点数,而Rat是一个有理数(存储为整数分子和分母)。文字123.4567890不是Num,而是RatNum文本总是有一个e指数部分(如123.45e1)。但是,由于这里的问题是处理货币,所以Rat实际上是正确的选择,所以我会更改代码以使用Rat类型,而不是Num,并保持原样。
  • 在Perl 6中,toString方法将更自然地命名为Str。类型定义了它们是如何通过编写一个具有该类型名称的方法来强制其他事情的。如果在字符串中插入Price实例,或者与~前缀运算符一起使用,那么调用它就意味着会自动调用它。
  • 需要构造一个异常,所以die(X::Price::PriceInvalid(:$price));应该是die(X::Price::PriceInvalid.new(:$price));
票数 8
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/50788338

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档