scalalandio / samurai   1.1

Apache License 2.0 GitHub

SAM-like macro annotation for Scala

Scala versions: 2.12 2.11
Scala.js versions: 0.6

Samurai

Build Status Maven Central License

SAM-like macro annotation for Scala

Note this project is experimental and probably doesn't cover all cases correctly.

You can use compiler-supported SAM even in Scala 2.11 with -Xexperimental flag.

This project will not be loger maintained.

Scala 2.12 introduced simplified syntax for defining instances of classes/traits that contain single abstract method. Considering following interface:

trait Show[T] {
  def show(x: T): String
}

Instead of

val intShow: Show[Int] = new Show[Int] {
  def show(x: Int): String = x.toString
}

we can simply write

val intShow: Show[Int] = (x: Int) => x.toString

Unfortunately, in Scala 2.11 such a syntax is not possible and for every type we have to either define separate helper function or live with this boilerplate. It's possible with -Xexperimental.

Samurai library defines macro annotation called @sam that brings this nice syntax also Scala 2.11.

Setup

You have to add following lines to your build.sbt:

libraryDependencies += "io.scalaland" %% "samurai" % "1.1" // triple %%% if you're on Scala.js

resolvers += Resolver.sonatypeRepo("releases")
addCompilerPlugin("org.scalamacros" % "paradise" % "2.1.1" cross CrossVersion.full)

Usage

Just prepend your val/def definitions with @sam annotation.

import samurai._
@sam val intShow: Show[Int] = (x: Int) => x.toString

This will expand in compile-time to the following piece of code:

val intShow: Show[Int] = new Show[Int] {
  def show(x: Int): String = x.toString
}

Advanced example

import samurai._
@sam implicit val intShow: Show[Int] = (x: Int) => x.toString
@sam implicit val intStr: Show[String] = (x: String) => x

@sam implicit def tupleInst[A: Show, B: Show]: Show[(A, B)] =
  (p: (A, B)) => s"(${implicitly[Show[A]].show(p._1)}, ${implicitly[Show[B]].show(p._2)})"

Last definition will be expanded to something similar to:

implicit def tupleInst[A, B](implicit s1: Show[A], s2: Show[B]): Show[(A, B)] = 
  new Show[(A, B)] {
    def show(s: (A, B)): String = s"(${s1.show(p._1)}, ${s2.show(p._2)})"
  }

Quirks

I observed that this approach might not work for every local type definition, because of the quirks of scala reflection API, so recommended approach is to define all your traits/abstract classes that you want to instantiate with @sam annotation as top-level ones.

Cross compilation

The library is cross-compiled on 2.11 and 2.12. On Scala 2.11 it performs real expansion described above, while on 2.12 it relies on native support provided by compiler, so it actually doesn't transform the original definition in any way.