The idea of tag is that quite often you don't want to use raw Long
s, Int
s and so on everywhere - well, you want to use them in your code, but you don't want to pass them on interface level:
def fetchUsers(numberOfUsers: Int, offset: Int): Seq[User]
here you have to used named parameters in order to make sure that you didn't swap the order of arguments. Additionally someone might make the mistake of overriding it with wrong order:
override def fetchUsers(offset: Int, numberOfUsers: Int): Seq[User]
To avoid that you can use different types for both arguments. One way yo do it is to use case class with AnyVal
- if you follow few rules it is optimized by compiler to primitives. Tags are alternative method to introduce new types for some common types (that might not necessarily be primitives). @@
is defined as
type @@[A, T] = A
as you noticed. So you could define new type as:
@@[String, QueueURL.Marker]
but because Scala allow us to use infix syntax on types, we can write it also as:
String @@ QueueURL.Marker
It is especially useful if you make dependency injection with e.g. implicits or Macwire - there arguments are fetch based only on types, and so having distinguished type for each injectable value is a must (on a side-note Macwire implements it's own version of @@
- they did it slightly different way, but it serves the same purpose).
Then you can end up with a code like this:
def fetchUsers(numberOfUsers: Int @@ UsersNumber, offset: Int @@ Offset): Seq[User]
or:
type UsersNumber = Int @@ UsersNumberTag
type UsersOffset = Int @@ UsersOffsetTag
def fetchUsers(numberOfUsers: UsersNumber, offset: UsersOffset): Seq[User]
I also saw this variant:
type UsersNumber[T] = T @@ UsersNumberTag
type UsersOffset[T] = T @@ UsersOffsetTag
def fetchUsers(numberOfUsers: UsersNumber[Int], offset: UsersOffset[Int]): Seq[User]
Looking at the aws-scala code I assume they wanted to achieve the first property - being able to distinguish different usages of some common types (like String
) and use compiler to check if they didn't make a mistake.
Example in ammonite:
@ 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
https://github.com/scalaz/scalaz
doesn't seem to mention it. – Averil