假设您有一个笛卡尔坐标类型。
struct Point { x : f64 , y : f64 }
现在我们要添加一个方法来旋转它。这里有两种常见的模式:
impl Point { // Return a new point, don't modify the self parameter. fn rotate_new ( & self , rad : f64 ) -> Point ; // Modify self. fn rotate_self ( & mut self , rad : f64 ); }
这两个都是相当合理的,但是在某些情况下它们都是不方便的。
例如使用rotate_new
变体:
// Creating a new value is easy. turn_to ( angle .rotate_new ( amount )); // But modifying a variable creates duplication. angle = angle .rotate_new ( amount );
对于rotate_self
变体:
// Creating a new value is very annoying. let mut target = angle .clone (); target .rotate_self ( amount ); turn_to ( target ); // But modifying a variable is natural. angle .rotate_self ( amount );
你可以说突变是邪恶的,所以rotate_new
更好,但是在一种不是纯粹功能性的语言中,你会想要改变一些东西。此外,在某些情况下,额外的复制可能会非常昂贵。
那么我们如何定义一个满足所有用例的接口呢?最简单的解决方案是像我们上面所做的那样定义两者。但是,可能有更好的命名方案。 rotate
和rotate_new
, rotate
和with_rotation
怎么样?或者我们可以变得可爱并称它们为rotate
和rotated
?这可行,但它需要在界面中有大量重复的代码。此外,缺乏强大的命名约定使库的用户猜测方法被称为什么。
还有其他选择吗?事实证明,编程语言已经解决了这个问题!想想+
运算符。
turn ( angle + adjustment ); angle += adjustment ;
好的,不仅是+
运算符,还有+=
运算符。有了这两个,我们可以自然地处理这两种情况!对我们来说幸运的是,不仅仅是+
有+=
。根据您的语言,您可能对您的语言中的大多数运算符都有 op-assign 运算符! -=
, <<=
, ||=
…
但它(通常)是具有隐含含义的固定运算符列表。如果您重载<<
来旋转一个点只是因为您想利用<<=
,您的用户可能不会欣赏!
如果我们可以为函数调用使用 op-assign 运算符会怎样?呼叫分配操作员。语法需要稍微不同,因为我们需要在其中放一个函数名,但我认为我们可以找到一些看起来不太奇怪的东西。
angle .= rotate ( amount ); angle .= rotate ( amount ); angle .rotate = ( amount );
任你选。虽然.=
最接近于其他 op-assign 运算符,但我最喜欢thing.method=(...)
。然而,在许多语言中,它已经为属性分配了一个带括号的表达式,因此它可能不是总体上的最佳选择。
我认为这可以派上用场的一个地方是构建器模式。构建器模式是一种在没有关键字和可选参数的语言中模拟它们的方法。
let http = reqwest :: Client :: builder () .connect_timeout ( std :: time :: Duration :: from_secs ( 1 )) .timeout ( std :: time :: Duration :: from_secs ( 10 )) .build () ? ;
就个人而言,我觉得这种模式很烦人。它在“快乐的道路”中运行良好,但如果您有时需要设置一个选项,它就会崩溃。
let mut builder = reqwest :: Client :: builder () .connect_timeout ( std :: time :: Duration :: from_secs ( 1 )) .timeout ( std :: time :: Duration :: from_secs ( 10 )); if cfg .use_rustls { builder = builder .use_rustls_tls (); } let http = builder .build () ? ;
一旦你遇到这样的事情——你有一个条件或者想将一些选项委托给另一个函数——我发现简单的可变方法( & mut self
)最终会更简单和更一致。
// Assuming the API was changed. let mut builder = reqwest :: Client :: builder (); builder .connect_timeout ( std :: time :: Duration :: from_secs ( 1 )) builder .timeout ( std :: time :: Duration :: from_secs ( 10 )); if cfg .use_rustls { builder .use_rustls_tls (); } configure_client ( & mut builder ); let http = builder .build () ? ;
我认为 call-assign 可以很好地与现有的 API 配合使用。这意味着“快乐路径”的用户可以继续使用链接,如果他们愿意的话。但是像我这样的人可以使用简单的作业。
// Assuming the API was changed. let mut builder = reqwest :: Client :: builder (); builder .connect_timeout = ( std :: time :: Duration :: from_secs ( 1 )) builder .timeout = ( std :: time :: Duration :: from_secs ( 10 )); if cfg .use_rustls { builder .use_rustls_tls = (); } configure_client ( & mut builder ); let http = builder .build () ? ;
(出于性能原因,能够重载 call-assign 以便更有效地实现它是有意义的。但这只是一种优化。)
有没有支持这样的语言?他们是否有针对重要的地方分别优化 call 和 call-assign 实现的机制?