Recommended method for constraining class attributes?(建议的限制类属性的方法?)

huangapple go评论66阅读模式
英文:

Recommended method for constraining class attributes?

问题

我有一个类,其中包含一个字符串属性。该属性表示文件的路径。我想在构造对象之前确保该文件存在。将来,我可能还想对文件进行其他检查,比如它是否格式正确。

无论如何,如果文件不存在,我想抛出一个具有描述性信息的异常。

经过一些试验和错误,我想出了以下方法:

```perl
unit class Vim::Configurator;
sub file-check($file) {
    die (X::IO::DoesNotExist.new(:path($file), :trying('new'))) if !$file.IO.f.Bool;
    return True;
}

has Str:D $.file is required where file-check($_);

但我们都知道,有多种方法可以做到这一点。

另一种选择是将约束逻辑放入newbuild方法中。这样做是可以的,但感觉有点老派,我认为我更喜欢像第一个示例中那样,将每个属性的逻辑明确写在属性旁边。

第三种选择是:

has Str:D $.file is required where *.IO.f.Bool == True;

这很简洁,但抛出的错误信息很难理解。

第四种选择是使用subset来约束属性,例如:

subset Vim::Configurator::File where *.IO.f.Bool == True;

unit class Vim::Configurator;
has Vim::Configurator::File $.file is required;

这里抛出的错误消息也不是最好的。而且,我觉得这种方式有点奇怪。

我确信还有其他方法可以解决这个问题,我想知道其他人都在做什么,是否有比上面提到的任何方法更好的方法。谢谢。


<details>
<summary>英文:</summary>

I have a class with a string attribute. The attribute represents a path to a file. I want to ensure this file exists before constructing an object. In the future, I may also want to throw additional checks at the file, too, like whether or not it&#39;s formatted properly.

At any rate, if the file doesn&#39;t exist, I want to throw a descriptive exception. 

After some trial and error, I came up with this:

unit class Vim::Configurator;
sub file-check($file) {
die (X::IO::DoesNotExist.new(:path($file), :trying('new'))) if !$file.IO.f.Bool;
return True;
}

has Str:D $.file is required where file-check($_);


But there is more than one way to do this, as we all know.


Another option is to put the constraint logic into the `new` or `build` methods. This is OK, but this feels old school and I think I prefer having the logic for each attribute spelled out right alongside the attribute like in the first example. 


A third option:

has Str:D $.file is required where *.IO.f.Bool == True;


This is nice and concise, but the error thrown is very inscrutable.


A fourth option is to use `subset` to constrain the attribute with something like this:

subset Vim::Configurator::File where *.IO.f.Bool == True;

unit class Vim::Configurator;
has Vim::Configurator::File $.file is required;


The error message thrown here isn&#39;t the greatest either. Plus it just feels weird to me. 

I&#39;m sure there are other ways to skin this cat and I&#39;m wondering what others are doing and if there is anything superior to any of the methods mentioned above. Thanks.

</details>


# 答案1
**得分**: 8

第三种选项:

```perl
class A {
    has Str:D $.file where { .IO.f orelse .throw }
}

A.new(file => "absent");

这会得到类似于以下的结果:

Failed to find 'absolute/path/to/absent' while trying to do '.f'
  in block  at ...
  in block <unit> ...

在where子句中,$_ 是传递的文件字符串,即上面的 "absent"。然后检查它是否存在作为文件;.IO.f 如果找不到文件会失败,所以使用 orelse$_ 将成为它导致的 Failure,然后被 .throw

然而,如果传递的文件字符串确实存在,但不是文件,比如一个目录,那么 .IO.f 不会失败,而是返回 False!然后 orelse 将不会切换到 .throw,因为False是一个已定义的值。在这种情况下,我们回到了一个不太有帮助的消息。为此,我们可以首先检查文件的存在性,然后分别处理文件性质:

class A {
    has Str:D $.file where { .IO.e.not
                                ?? fail qq|"$_" does not exist at all|
                                !! .IO.f or fail qq|"$_" is not a file| };
}

然后:

>>> A.new(file => "absent")
"absent" does not exist at all
  in block ...

>>> A.new(file => "existing_dir")
"existing_dir" is not a file
  in block ...
英文:

> A third option:<BR>
> has Str:D $.file is required where *.IO.f.Bool == True;<BR>
> This is nice and concise, but the error thrown is very inscrutable.

You can have a block in the where clause and throw there:

class A {
    has Str:D $.file where { .IO.f orelse .throw }
}

A.new(file =&gt; &quot;absent&quot;);

which gives something like

Failed to find &#39;absolute/path/to/absent&#39; while trying to do &#39;.f&#39;
  in block  at ...
  in block &lt;unit&gt; ...

In the where clause, $_ is the passed file string, i.e., "absent" above. Then its existence-as-a-file is checked; .IO.f fails if it cannot locate the file, so with orelse, the $_ will be the Failure it resulted in, which is then .thrown.

However, if the passed file string does exist but it's not a file but, e.g., a directory, then .IO.f won't fail but instead return False! Then the orelse will not switch to .throw because False is a defined value. In this case, we are back to an unhelpful message. To this end, we can first check existence, and deal with fileness separately:

class A {
    has Str:D $.file where { .IO.e.not
                                ?? fail qq|&quot;$_&quot; does not exist at all|
                                !! .IO.f or fail qq|&quot;$_&quot; is not a file| };
}

and then

&gt;&gt;&gt; A.new(file =&gt; &quot;absent&quot;)
&quot;absent&quot; does not exist at all
  in block ...

&gt;&gt;&gt; A.new(file =&gt; &quot;existing_dir&quot;)
&quot;existing_dir&quot; is not a file
  in block ...

答案2

得分: 6

你可以覆盖默认的 TWEAKBUILD 方法来测试文件是否存在,如果不存在,则 die

class File {
    has Str $.path is required;
    
    submethod BUILD(:$path) {
        die '文件 $path 不存在!' unless $path.IO.e; # 检查与路径对应的文件是否存在
        $!path := $path;
    }
}

如果你想要更精细的控制,也可以编写一个工厂方法来进行检查,而不是在类级别进行检查。

更多阅读:https://docs.raku.org/language/objects#Object_construction

英文:

You can override the default TWEAK or BUILD methods to test if the file exists, if not, die.

class File {
    has Str $.path is required;
    
    submethod BUILD(:$path) {
        die &#39;File $path does not exist!&#39; unless $path.IO.e; # Check if the file corresponding to the path exists
        $!path := $path;
    }
}

If you want more fine grained control, you can also write a factory that does the checking rather than doing it on the class level.

Further reading: https://docs.raku.org/language/objects#Object_construction

答案3

得分: 0

TL;DR will complain特性是为了处理类似这种情况而设计的,尽管在编写本答案时(2023年3月)它是实验性的,并且没有文档记录。

您写道:

> 第三个选项:

has Str:D $.file is required where *.IO.f.Bool == True;

> 这很简洁,但抛出的错误非常难以理解。

will complain特性通过允许编码人员指定错误消息来解决了理解困难的问题:

use experimental :will-complain;

has Str:D $.file is required
  will complain("$file does not exist!")
  where *.IO.f.Bool == True;
英文:

TL;DR The will complain trait was designed for cases like this, though at the time of writing this answer (March 2023) it's experimental status and undocumented.

You wrote:

> A third option:

has Str:D $.file is required where *.IO.f.Bool == True;

> This is nice and concise, but the error thrown is very inscrutable.

The will complain trait addresses the inscrutability problem by letting the coder specify the error message:

use experimental :will-complain;

has Str:D $.file is required
  will complain(&quot;$file does not exist!&quot;)
  where *.IO.f.Bool == True;

huangapple
  • 本文由 发表于 2023年1月9日 08:59:46
  • 转载请务必保留本文链接:https://go.coder-hub.com/75052363.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定