使用 Scala 隐式编码风格
是否有任何风格指南描述如何使用 Scala 隐式编写代码?
隐式确实很强大,因此很容易被滥用。是否有一些通用准则说明何时适合隐式以及何时使用它们会使代码变得模糊?
Are there any style guides that describe how to write code using Scala implicits?
Implicits are really powerful, and therefore can be easily abused. Are there some general guidelines that say when implicits are appropriate and when using them obscures code?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
这个产品鲜为人知,以至于还没有被命名(据我所知),但它已经成为我个人最喜欢的产品之一。
因此,我将在这里冒险,并将其命名为“pimp my type class”模式。也许社区会想出更好的东西。
这是一个由 3 部分组成的模式,完全由隐式构建。它也已经在标准库中使用(自 2.9 起)。此处通过大量精简的 Numeric 类型类进行解释,希望您应该熟悉它。
第 1 部分 - 创建类型类
第 2 部分 - 添加提供中缀操作的嵌套类
第 3 部分 - 使用操作对类型类的成员进行 Pimp
然后使用它
完整代码:
This one is so little-known that it has yet to be given a name (to the best of my knowledge), but it's already firmly established as one of my personal favourites.
So I'm going to go out on a limb here, and name it the "pimp my type class" pattern. Perhaps the community will come up with something better.
This is a 3-part pattern, built entirely out of implicits. It's also already used in the standard library (since 2.9). Explained here via the heavily cut-down
Numeric
type class, which should hopefully be familiar.Part 1 - Create a type class
Part 2 - Add a nested class providing infix operations
Part 3 - Pimp members of the type class with the operations
Then use it
Full code:
我认为还没有一种社区范围的风格。我见过很多约定。我将描述我的,并解释为什么我使用它。
命名
我将隐式转换称为其中之一,
我不希望显式使用这些转换,因此我倾向于使用相当长的名称。不幸的是,类名中经常出现数字,因此
whatwehave2whatwegenerate
约定会变得混乱。例如:tuple22myclass
——您所说的是Tuple2
还是Tuple22
?如果隐式转换的定义远离参数和转换结果,我总是使用 x_to_y 表示法以获得最大的清晰度。否则,我更多地将这个名字视为评论。因此,例如,
我使用类名和隐式作为关于代码要点的注释,即向对添加一个
fold
方法(即元组2
)。用法
Pimp-My-Library
对于 pimp-my-library 风格的构造,我最常使用隐式转换。我在所有地方都这样做,它添加了缺少的功能或使生成的代码看起来更干净。
现在,隐式转换会带来性能损失,因此我不会以这种方式在热点中编写代码。但除此之外,一旦我超出了相关代码中的少数用途,我很可能会使用 pimp-my-library 模式而不是 def。
还有另一个考虑因素,即工具在显示隐式转换的来源方面还不如显示方法的来源那么可靠。因此,如果我正在编写困难的代码,并且我希望任何使用或维护它的人都必须努力学习以理解所需的内容以及它是如何工作的,我——这几乎是从典型的 Java 哲学——我更有可能以这种方式使用 PML 来使步骤对受过训练的用户更加透明。注释会警告代码需要深入理解;一旦你深入了解,这些改变就会带来帮助而不是伤害。另一方面,如果代码做的事情相对简单,我更有可能将定义保留在适当的位置,因为如果我们需要进行更改,IDE 将帮助我或其他人快速上手。
避免显式转换
我尝试避免显式转换。你当然可以写,
但这是非常危险的,即使你似乎用 .toInt 填充所有字符串。
我提出的主要例外是包装类。例如,假设您希望有一个方法采用带有预先计算的哈希码的类。我会
自动取回我开始使用的任何类,或者最坏的情况是通过添加类型注释(例如
x: String
)。原因是这使得包装类的侵入最小化。你并不是真的想了解包装器;你真的想了解包装器。有时你只需要功能。您无法完全避免注意到包装器(例如,您只能在一个方向上修复等于,有时您需要返回到原始类型)。但这通常可以让您轻松地编写代码,有时这正是您要做的事情。隐式参数
隐式参数的混杂程度令人震惊。只要有可能,我都会使用默认值。但有时你不能,特别是对于通用代码。
如果可能的话,我尝试使隐式参数成为其他方法不会使用的参数。例如,Scala 集合库有一个
CanBuildFrom
类,除了集合方法的隐式参数之外,它几乎完全没有任何用处。因此,意外串扰的危险很小。如果这是不可能的 - 例如,如果需要将参数传递给多个不同的方法,但这样做确实会分散代码正在执行的操作(例如尝试在算术中间进行日志记录),那么不要使一个公共类(例如
String
)是隐式val,我将它包装在一个标记类中(通常使用隐式转换)。I don't think there is a community-wide style yet. I've seen lots of conventions. I'll describe mine, and explain why I use it.
Naming
I call my implicit conversions one of
I don't expect these to be used explicitly, so I tend towards rather long names. Unfortunately, there are numbers in class names often enough so the
whatwehave2whatwegenerate
convention gets confusing. For example:tuple22myclass
--is thatTuple2
orTuple22
you're talking about?If the implicit conversion is defined away from both the argument and result of the conversion, I always use the
x_to_y
notation for maximum clarity. Otherwise, I view the name more as a comment. So, for instance, inI use both the class name and the implicit as a sort of a comment about what the point of the code is--namely to add a
fold
method to pairs (i.e.Tuple2
).Usage
Pimp-My-Library
I use implicit conversions the most for pimp-my-library style constructions. I do this all over the place where it adds missing functionality or makes the resulting code look cleaner.
Now, there is a performance penalty to pay for implicit conversions, so I don't write code in hotspots this way. But otherwise, I am very likely to use a pimp-my-library pattern instead of a def once I go above a handful of uses in the code in question.
There is one other consideration, which is that tools are not as reliable yet at showing where your implicit conversions come from as where your methods come from. Thus, if I'm writing code that is difficult, and I expect that anyone who is using or maintaining it is going to have to study it hard to understand what is required and how it works, I--and this is almost backwards from a typical Java philosophy--am more likely to use PML in this fashion to render the steps more transparent to a trained user. The comments will warn that the code needs to be understood deeply; once you understand deeply, these changes help rather than hurt. If, on the other hand, the code's doing something relatively straightforward, I'm more likely to leave defs in place since IDEs will help me or others quickly get up to speed if we need to make a change.
Avoiding explicit conversions
I try to avoid explicit conversions. You certainly can write
but it's awfully dangerous, even if you seem to be peppering all your strings with .toInt.
The main exception I make is for wrapper classes. Suppose, for example, you want to have a method take classes with a pre-computed hash code. I would
and get back whatever class I started with either automatically or, at worst, by adding a type annotation (e.g.
x: String
). The reason is that this makes wrapper classes minimally intrusive. You don't really want to know about the wrapper; you just need the functionality sometimes. You can't completely avoid noticing the wrapper (e.g. you can only fix equals in one direction, and sometimes you need to get back to the original type). But this often lets you write code with minimal fuss, which is sometimes just the thing to do.Implicit parameters
Implicit parameters are alarmingly promiscuous. I use default values whenever I possibly can instead. But sometimes you can't, especially with generic code.
If possible, I try to make the implicit parameter be something that no other method would ever use. For example, the Scala collections library has a
CanBuildFrom
class that is almost perfectly useless as anything other than an implicit parameter to collections methods. So there is very little danger of unintended crosstalk.If this is not possible--for example, if a parameter needs to be passed to several different methods, but doing so really distracts from what the code is doing (e.g. trying to do logging in the middle of arithmetic), then rather than make a common class (e.g.
String
) be the implicit val, I wrap it in a marker class (usually with an implicit conversion).我不相信我遇到过任何东西,所以让我们在这里创建它!一些经验法则:
隐式转换
当从
A
隐式转换为B
时,并非每个A
都会出现这种情况> 可以被视为B
,通过拉皮条toX
转换或类似的方式来实现。例如:别生气!用于非常常见的核心库功能,而不是在每个类中为了它而拉皮条一些东西!
隐式参数
使用这些来:
ExecutorService
)不要为了懒惰而使用!
I don't believe I have come across anything, so let's create it here! Some rules of thumb:
Implicit Conversions
When implicitly converting from
A
toB
where it is not the case that everyA
can be seen as aB
, do it via pimping atoX
conversion, or something similar. For example:Don't go mad! Use for very common core library functionality, rather than in every class to pimp something for the sake of it!
Implicit Parameters
Use these to either:
ExecutorService
to some worker invocation)Don't use for laziness' sake!