Exploration of Scala macros
Scala
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Failed to load latest commit information.
ground/src/main/scala/com/github/retronym/macrocosm
project
src upgrade to scala 2.10.1 Mar 25, 2013
.gitignore ignore local .sbt foldeR Mar 10, 2012
README.md

README.md

An exploration of Scala macros.

This branch has been adapted for the new syntax proposed in SIP-16

Prerequisites

SBT 0.12.0

Use the latest launcher. You might want to start SBT without using your global ~/.sbt folder with incompatible plugins.

The easiest way to obtain the right SBT version, and to use a separate .sbt directory is to use Paul Phillips' sbt-extras launcher script.

Now, the macros!

Compiler Diagnostics / Tree Manipulation

> console
[info] Compiling 1 Scala source to /Users/jason/code/scala-spock/target/scala-2.10.0-SNAPSHOT/classes...
[info] Starting scala interpreter...
[info]
Welcome to Scala version 2.10.0-M1-0362-g2fe570291a-2012-02-18 (Java HotSpot(TM) 64-Bit Server VM, Java 1.6.0_29).
Type in expressions to have them evaluated.
Type :help for more information.

scala> import com.github.retronym.macrocosm.Macrocosm._
import com.github.retronym.macrocosm.Macrocosm._

scala> desugar("foo".map(_ + 1))
res0: String = scala.this.Predef.augmentString("foo").map[Int, Any](((x$1: Char) => x$1.+(1)))(scala.this.Predef.fallbackStringCanBuildFrom[Int])

The following work without macrocosm.

scala> val t = reflect.mirror.reify("abc".reverse)
t: reflect.mirror.Expr[String] = Expr[null](scala.this.Predef.augmentString("abc").reverse)

scala> reflect.mirror.showRaw(t.tree)
res3: String = Select(Apply(Select(Select(This(newTypeName("scala")), newTermName("Predef")), newTermName("augmentString")), List(Literal(Constant("abc")))), newTermName("reverse"))

scala> val tb = new reflect.runtime.Mirror.ToolBox()
tb: reflect.runtime.Mirror.ToolBox = scala.reflect.runtime.ToolBoxes$ToolBox@55b6ed96

scala> tb.typeCheck(t.tree)
res5: reflect.mirror.Tree = scala.this.Predef.augmentString("abc").reverse

scala> .tpe
res6: reflect.mirror.Type = String

scala> tb.runExpr(t.tree)
res8: Any = cba

Especially fun is using desugar to see what the other macros here have done!

scala> desugar{def foo[T: Numeric](t: T) = t - t}
res7: String = 
{
  def foo[T >: Nothing <: Any](t: T)(implicit evidence$1: Numeric[T]): T = evidence$1.minus(t, t);
  ()
}

scala> desugar{var i = 0; cfor(0)(_ < 10, _ + 1)((a: Int) => i += 1)}
res8: String = 
{
  var i: Int = 0;
  {
    var $a: Int = 0;
    while$1(){
      if ({
        val x$1: Int = $a;
        x$1.<(10)
      })
        {
          {
            {
              val a: Int = $a;
              i = i.+(1)
            };
            $a = {
              val x$2: Int = $a;
              x$2.+(1)
            }
          };
          while$1()
        }
      else
        ()
    }
  }
}

Logging / Assertions

scala> log("".isEmpty)
"".isEmpty() = true
res1: Boolean = true

scala> assert1("foo".reverse == "off")
java.lang.AssertionError: assertion failed: scala.this.Predef.augmentString("foo").reverse.==("off")
    at scala.Predef$.assert(Predef.scala:161)
    at .<init>(<console>:10)

scala> def plus(a: Int, b: Int) = a + b
plus: (a: Int, b: Int)Int

scala> trace(plus(1, plus(2, plus(3, 4))))
$line1.$read.$iw.$iw.plus(3, 4) = 7
$line1.$read.$iw.$iw.plus(2, $line1.$read.$iw.$iw.plus(3, 4)) = 9
$line1.$read.$iw.$iw.plus(1, $line1.$read.$iw.$iw.plus(2, $line1.$read.$iw.$iw.plus(3, 4))) = 10
res3: Int = 10

New literals

scala> b"101010"
res4: Int = 42

scala> b"102"
<console>:11: error: exception during macro expansion: invalid binary literal
              b"102"
              ^

Statically Checked Regex

scala> regex(".*")
res0: scala.util.matching.Regex = .*

scala> regex("{")
<console>:11: error: exception during macro expansion: Illegal repetition
{
              regex("{")
                   ^

High Performance Loops

Translated to while loops, eliminate the indirection through FunctionN, and avoids boxing.

scala> cfor(0)(_ < 10, _ + 2)(println(_))
0
2
4
6
8

scala> val as = Array(1, 2, 3)
as: Array[Int] = Array(1, 2, 3)

scala> iteratorForeach(Iterator(1, 2, 3, 4, 5))(println(_))
1
2
3
4
5

Implicit Wrapper Elimination

Rewrite enrich[T](lhs)(numericInstance).*(rhs) to numericInstance.plus(lhs, rhs) by defining * as a macro def.

scala> import com.github.retronym.macrocosm.Macrocosm._
import com.github.retronym.macrocosm.Macrocosm._

scala> object A { def foo[T: Numeric](t: T) = (-t * t).abs }
 public java.lang.Object foo(java.lang.Object, scala.math.Numeric);
  Code:
   Stack=4, Locals=3, Args_size=3
   0:   aload_2
   1:   aload_2
   2:   aload_2
   3:   aload_1
   4:   invokeinterface #21,  2; //InterfaceMethod scala/math/Numeric.negate:(Ljava/lang/Object;)Ljava/lang/Object;
   9:   aload_1
   10:  invokeinterface #25,  3; //InterfaceMethod scala/math/Numeric.times:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
   15:  invokeinterface #28,  2; //InterfaceMethod scala/math/Numeric.abs:(Ljava/lang/Object;)Ljava/lang/Object;
   20:  areturn
  LineNumberTable:
   line 10: 2

Dynamic Lens Creation

This even works for case classes defined in the current compile run.

scala> case class Person(name: String, age: Int)
defined class Person

scala> val nameLens = lens[Person].name
dynatype: com.github.retronym.macrocosm.Macrocosm.lens[Person].applyDynamic("name")
TypeApply(Select(Select(Select(Select(Select(Ident(newTermName("com")), newTermName("github")), newTermName("retronym")), newTermName("macrocosm")), newTermName("Macrocosm")), newTermName("lens")), List(TypeTree().setType(Person)))
nameLens: (Person => String, (Person, String) => Person) = (<function1>,<function2>)

scala> val p = Person("brett", 21)
p: Person = Person(brett,21)

scala> val nameLens = lens[Person].name
dynatype: com.github.retronym.macrocosm.Macrocosm.lens[Person].applyDynamic("name")()
TypeApply(Select(Select(Select(Select(Select(Ident(newTermName("com")), newTermName("github")), newTermName("retronym")), newTermName("macrocosm")), newTermName("Macrocosm")), newTermName("lens")), List(TypeTree().setType(Person)))
nameLens: (Person => String, (Person, String) => Person) = (<function1>,<function2>)

scala> nameLens._1(p)
res1: String = brett

scala> nameLens._2(p, "bill")
res2: Person = Person(bill,21)

scala> lens[Person].namexx(())
dynatype: com.github.retronym.macrocosm.Macrocosm.lens[Person].applyDynamic("namexx")()
TypeApply(Select(Select(Select(Select(Select(Ident(newTermName("com")), newTermName("github")), newTermName("retronym")), newTermName("macrocosm")), newTermName("Macrocosm")), newTermName("lens")), List(TypeTree().setType(Person)))
error: exception during macro expansion: value namexx is not a member of Person