Simple smart constructor generation for Scala.
Copyright 2015 Dave Gurnell. Licensed Apache 2.
Add the following to your build.sbt:
libraryDependencies += "com.davegurnell" %% "smartypants" % "<<VERSION>>"You also need to add the Macro Paradise compiler plugin to your project
(otherwise the @smart annotation won't do anything):
addCompilerPlugin("org.scalamacros" % "paradise" % "2.1.0" cross CrossVersion.full)Smartypants provides a @smart macro
to quickly define "smart constructors" for algebraic data types:
import smartypants._
sealed abstract class User extends Product with Serializable {
def cookie: String
}
object User {
@smart case class Anonymous(cookie: String) extends User
@smart case class LoggedIn(email: String, cookie: String) extends User
}
val a = User.Anonymous("aCookie")
// a: User.Anonymous = Anonymous(aCookie)
val b = User.anonymous("aCookie")
// b: User = Anonymous(aCookie)In this example, the @smart annotations
define two constructor methods
called anonymous and loggedIn.
Each method takes the same number of parameters
as its respective class
and returns an instance of the class typed as a User.
The main use case for smart constructors is as a workaround for unhelpful type inference. For example:
implicit val userOrdering: Ordering[User] =
Ordering.by(_.cookie)
val users = List(User.Anonymous("cookie2"), User.Anonymous("cookie1"))
// users: List[User.Anonymous] = List(Anonymous(cookie2), Anonymous(cookie1))
val sorted = users.sorted
// <console>:17: error: No implicit Ordering defined for User.Anonymous.
// users.sorted
// ^The problem here is that users is of type List[User.Anonymous],
not List[User] as our ordering requires.
We can fix this issue by inserting type annotations:
(users : List[User]).sortedbut our smart constructors let us bypass the problem altogether:
val users1 = List(User.anonymous("cookie2"), User.anonymous("cookie1"))
// users1: List[User] = List(Anonymous(cookie2), Anonymous(cookie1))
val sorted1 = users1.sorted
// sorted1: List[User] = List(Anonymous(cookie1), Anonymous(cookie2))You can use @smart to annotate any inner class or object.
The @smart macro infers the name and type of the constructor
from the annotated class or object:
// name is foo, return type is Bar
@smart case object Foo extends Bar
// name is baz, return type is Bar
@smart case class Baz(value: Int) extends BarYou can customise the generated name by providing a value parameter:
@smart("alternativeName") case object Foo extends Barand the return type by providing a type parameter:
@smart[AnyRef] case class Baz(value: Int) extends BarYou can even customise both at the same time:
@smart[AnyRef]("blah") case class Baz(value: Int) extends BarSee the tests for more examples.