When browsing a piece of Scala code at aws-scala by Atlassian you can find the following line:
type QueueURL = String @@ QueueURL.Marker
I am new to Scala, so I might be wrong, but the @@
(double at-sign) doesn't seem like a standard built-in Scala operator. Moreover, an observant reader will find it imported from 'scalaz' library:
import scalaz.{ Tag, @@ }
What does the @@
do? And why is it used?
As mentioned in the comments, the actual definition is:
type @@[A, T] = A
which might be some hint.
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
Tagging makes creating new types easy. It uses the @@ symbol to "tag" an existing type as another type (in other words, it creates a new type). So String @@ Text should be read as "String tagged with Text". Not sure why it is used.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With