标签的想法是,很多时候你不想在任何地方使用原始的Longs、Ints 等等——好吧,你想在你的代码中使用它们,但你不想在接口级别传递它们:
def fetchUsers(numberOfUsers: Int, offset: Int): Seq[User]
这里你必须使用命名参数来确保你没有交换参数的顺序。此外,有人可能会错误地用错误的顺序覆盖它:
override def fetchUsers(offset: Int, numberOfUsers: Int): Seq[User]
为避免这种情况,您可以对两个参数使用不同的类型。一种方法是使用带有AnyVal 的案例类 - 如果您遵循一些规则,它会被编译器优化为原语。标签是为一些常见类型(可能不一定是原语)引入新类型的替代方法。 @@ 定义为
type @@[A, T] = A
如你所见。所以你可以将新类型定义为:
@@[String, QueueURL.Marker]
但是因为 Scala 允许我们在类型上使用中缀语法,所以我们也可以将其写成:
String @@ QueueURL.Marker
如果您使用例如进行依赖注入,这将特别有用。隐含或 Macwire - 参数仅基于类型获取,因此必须为每个可注入值具有区分类型(在旁注中,Macwire 实现了它自己的 @@ 版本 - 他们的做法略有不同,但它目的相同)。
然后你可以得到这样的代码:
def fetchUsers(numberOfUsers: Int @@ UsersNumber, offset: Int @@ Offset): Seq[User]
或:
type UsersNumber = Int @@ UsersNumberTag
type UsersOffset = Int @@ UsersOffsetTag
def fetchUsers(numberOfUsers: UsersNumber, offset: UsersOffset): Seq[User]
我也看到了这个变种:
type UsersNumber[T] = T @@ UsersNumberTag
type UsersOffset[T] = T @@ UsersOffsetTag
def fetchUsers(numberOfUsers: UsersNumber[Int], offset: UsersOffset[Int]): Seq[User]
查看 aws-scala 代码,我假设他们想要实现第一个属性 - 能够区分某些常见类型的不同用法(如 String)并使用编译器检查它们是否没有出错。
菊石中的例子:
@ type @@[A, T] = A with T
defined type @@
@ trait Username
defined trait Username
@ def passUsername(username: String @@ Username): Unit = ()
defined function passUsername
@ passUsername("test")
cmd3.sc:1: type mismatch;
String|String with Username
val res3 = passUsername("test")
^
Compilation Failed