scalaz 中定义的这个 @@ 运算符是什么?

What is this @@ operator defined in scalaz?

浏览 piece of Scala code at aws-scala by Atlassian 时,您可以找到以下行:

type QueueURL = String @@ QueueURL.Marker

我是 Scala 的新手,所以我可能是错的,但是 @@(双 at 符号)看起来不像是标准的内置 Scala 运算符。此外,细心的 reader 会发现它是从 'scalaz' 库中导入的:

import scalaz.{ Tag, @@ }

@@ 有什么作用?为什么要使用它?

如评论中所述,actual definition is:

 type @@[A, T] = A

这可能是一些提示。

标记使创建新类型变得容易。它使用@@ 符号将现有类型 "tag" 作为另一种类型(换句话说,它创建了一个新类型)。所以String @@ Text应该读作"String tagged with Text"。不确定为什么使用它。

标签的想法是,您通常不想在任何地方使用原始 Longs、Ints 等等 - 好吧,您想在代码中使用它们,但您不想在接口级别传递它们:

def fetchUsers(numberOfUsers: Int, offset: Int): Seq[User]

在这里你必须使用命名参数以确保你没有交换参数的顺序。此外,有人可能会错误地以错误的顺序覆盖它:

override def fetchUsers(offset: Int, numberOfUsers: Int): Seq[User]

为避免这种情况,您可以为两个参数使用不同的类型。你这样做的一种方法是使用 case class 和 AnyVal - 如果你遵循一些规则,它会被编译器优化为原语。标签是为某些常见类型(可能不一定是原始类型)引入新类型的替代方法。 @@ 定义为

type @@[A, T] = A

如您所见。所以你可以将新类型定义为:

@@[String, QueueURL.Marker]

但因为 Scala 允许我们在类型上使用中缀语法,我们也可以将其写为:

String @@ QueueURL.Marker

如果你使用例如依赖注入,它特别有用。 implicits 或 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