【问题标题】:Why is adding methods to a type different than adding a sub or an operator in perl6?为什么向类型添加方法与在 perl6 中添加子或运算符不同?
【发布时间】:2019-05-06 19:55:57
【问题描述】:

使子程序/过程可供重用是模块的核心功能之一,我认为这是一种语言如何可组合并因此在程序员时间上高效的基本方式:

如果你在你的模块中创建一个类型,我可以创建我自己的模块来添加一个对你的类型进行操作的子。我不必扩展你的模块来做到这一点。

# your module
class Foo {
    has $.id;
    has $.name;
}

# my module
sub foo-str(Foo:D $f) is export {
    return "[{$f.id}-{$f.name}]"
}

# someone else using yours and mine together for profit
my $f = Foo.new(:id(1234), :name("brclge"));
say foo-str($f);

正如Overloading operators for a class 中所见,这种模块的可组合性同样适用于运算符,这对我来说是有道理的,因为无论如何运算符只是 subs 的某种语法糖(至少在我的脑海中)。请注意,此类运算符的定义不会导致现有代码的行为发生任何令人惊讶的变化,您需要将其显式导入代码中才能访问它,就像上面的子代码一样。

鉴于此,我觉得很奇怪我们没有类似的方法机制,例如How do you add a method to an existing class in Perl 6? 的讨论,特别是因为 perl6 是一种方法快乐的语言。如果我想扩展现有类型的使用,我想以与编写原始模块相同的样式来执行此操作。如果 Int 上有一个 .is-prime,我必须可以添加一个.is-semi-prime 也是,对吧?

我阅读了上面链接中的讨论,但不太相信“远距离行动”的论点:这与我从模块中导出另一个多子有什么不同?例如,使这成为词法更改(Trait + impl ... for)的 rust 方式对我来说似乎很卫生,并且非常符合上面的 operator 方法。

比技术性更有趣(至少对我而言)是语言设计的问题:为现有名词(类型)提供新动词(subs,operators,methods)的能力不是语言的核心设计目标吗?像perl6?如果是,为什么它会以不同的方式对待方法?如果它确实出于充分的理由对它们进行了不同的处理,这是否意味着我们将许多不可组合的方法用作名词,而我们应该使用 subs 来代替?

【问题讨论】:

  • 也许会进一步混淆:方法与后缀运算符有何不同或为什么不同?
  • 我不完全确定我理解这个问题。如果要添加 .is-semi-prime,可以:augment class Int { method is-semi-prime {…}}。对于与后缀的直接并行,您只需添加一个 multi 方法。
  • @guifa:当然你可以做扩充,但它不是你可以导入并因此组成的东西,它是卫生的或词法范围的,因此可以推断出它的作用范围。我想这就是它需要 MONKEY-TYPING 的部分原因。这一切都很好,但我发现它与潜艇和操作员的工作方式非常不一致......

标签: raku


【解决方案1】:

从语言设计的角度来看,这一切都归结为一个简单的问题:我们说的是哪种语言?在 Perl 6 中,这是一个我们总是试图非常清楚的问题。

Perl 6 中当前语言 的概念完全是根据词法范围定义的。子声明是词法范围的。当我们从模块中导入符号时,包括额外的 multi 候选者,它们是词法范围的。当我们进行语言调整时——比如引入新的运算符——这些都是词法范围的。我们当前语言中的动词——即子程序调用——是那些有词汇定义的动词。 (操作符只是sub 调用,具有更有趣的解析。)由于词法范围在编译时间结束时关闭,编译器对当前语言有一个完整的视图。这就是为什么在编译时检测和报告对不存在的subs 的子调用或对未声明变量的引用,以及一些基本的编译时类型检查;未来的 Perl 6 版本可能会扩展可以预期的编译时检查集。当前语言是静态的、早期绑定的 Perl 6 的一部分。

相比之下,方法调用是要以目标对象的语言解释的动词。这是 Perl 6 的动态的、后期绑定的部分。虽然最直接的结果是在 OO 的实现中以各种形式发现的典型多态性,但由于元编程,甚至解释动词的方式也出现了抢夺。例如,monitor 将在解释动词时获取锁,然后释放它。其他对象可能是constructed based on things other than Perl 6 code,因此对动词的解释并不意味着调用编写为 Perl 6 方法的代码。或者代码可能在网络上的某个地方。谁知道?好吧,当然不是调用者,这就是后期绑定的重点、力量和风险。

Perl 6 对“我想在我当前的语言中扩展我可以与这个对象一起使用的动词范围”的答案非常简单:使用与扩展当前语言相关的语言特性!甚至还有一个特殊的语法$obj.&foo,它允许在当前语言中定义一个动词foo——通过编写一个sub——然后就像它是对象上的一个方法一样调用它。然而,细微的句法区别让读者和编译器清楚地知道发生了什么,以及使用哪种语言来定义该动词。

通过使用augment可以扩展由某种类型的对象定义的语言。但是,它很少是最好的做事方式,因为它会产生全局影响,并且还会分散对象语言的定义。

我们在编程中所做的大部分工作都是关于构建语言。我的意思不是新语法。我们的大多数新语言——即使是像 Perl 6 那样易于变异的语言——只是使用标准语言特征定义的名词和动词。然而,在任何重要的程序中,我们不可能同时记住每种语言的每一个细节。当我去餐厅点炸肉排时,我不知道订单将如何运送到厨房,厨房是什么样子,炸肉排是按要求敲出、裹面包屑和烹制的,还是只是从准备好的炸肉排(希望不会太陈旧)缓存。厨房和我有足够的共同意义来让正确的事情发生,但我不知道他们会如何准确地回应我的要求,他们也不需要知道我在此期间会做什么。 OO 本身承认这种想法 - 至少在我们完全接受它时 - 并且在更大范围内通过领域驱动设计中的限界上下文等概念得到承认。

总之,Perl 6 试图帮助我们保持语言简洁:了解我们当前语言中的内容,以及我们仅在有限的理解下表达的内容。这种区别是由sub/method 区别编码的,这也是一个悬挂静态/动态区别的明智之选。

【讨论】:

  • 那么为什么 SO 没有用于语言设计者的后缀符号(如 mod 菱形)? :) 感谢权威解答!
  • 谢谢,这是 - 一如既往 - 一个很好的答案。我非常喜欢“我的语言”与“你的语言”的想法,我想这是我们在更多地使用 Perl 6 时需要更深入地理解的东西。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-09-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-01-26
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多