Skip to content

retronym/macrocosm

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

57 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

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