What is this @@ operator defined in scalaz?
Asked Answered
A

2

7

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.

Averil answered 22/2, 2017 at 15:58 Comment(2)
https://github.com/scalaz/scalaz doesn't seem to mention it.Averil
It's defined here.Mansfield
C
11

The idea of tag is that quite often you don't want to use raw Longs, Ints 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
Carmine answered 23/2, 2017 at 10:52 Comment(3)
Could you please make changes to the above examples with tags which help to avoid the error you mentionedHalland
Could you also show the compile errors when wrong types are used?Apology
Added, but just in case: it's 2020, if you need to use functionality provided by tagged types you should probably use @newtype or if you are on Dotty opaque typesCarmine
H
2

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.

Heliotrope answered 22/2, 2017 at 15:59 Comment(2)
What do you mean by 'tagging'? Like wrapping - similar to what Option does? (from type point of view)Averil
@GrzegorzOledzki Tagging means giving a special meaning to a type.Mccabe

© 2022 - 2024 — McMap. All rights reserved.