英文:
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($_);
但我们都知道,有多种方法可以做到这一点。
另一种选择是将约束逻辑放入new
或build
方法中。这样做是可以的,但感觉有点老派,我认为我更喜欢像第一个示例中那样,将每个属性的逻辑明确写在属性旁边。
第三种选择是:
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's formatted properly.
At any rate, if the file doesn'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't the greatest either. Plus it just feels weird to me.
I'm sure there are other ways to skin this cat and I'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 => "absent");
which gives something like
Failed to find 'absolute/path/to/absent' while trying to do '.f'
in block at ...
in block <unit> ...
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 .throw
n.
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|"$_" does not exist at all|
!! .IO.f or fail qq|"$_" is not a file| };
}
and then
>>> 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 ...
答案2
得分: 6
你可以覆盖默认的 TWEAK
或 BUILD
方法来测试文件是否存在,如果不存在,则 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 'File $path does not exist!' 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("$file does not exist!")
where *.IO.f.Bool == True;
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论