Skip to content
ochafik edited this page May 1, 2012 · 9 revisions

Compilets : compiler plugins for humans

Scalaxy processes compilets.

Compilets are a way to achieve a very useful subset of what compiler plugins can do, in a highly natural way (made possible by the upcoming Scala 2.10 macros and expression trees)

  • Intuitive, declarative patterns (just write the Scala code you're trying to match)
  • Define AST rewrites to make Scala or DSLs faster, or to add new functionality (without any knowledge of compiler internals)
  • Add your own errors or warnings to enforce good library / corporate practices
  • Pluggable architecture : any library can embed its own compilets, bundled within its regular JAR (will be detected by Scalaxy's compiler plugin and processed during compilation)

Target audience :

  • Library developers who want their DSLs to be fast and/or need to provide additional errors / warnings during compilation (see this post about the cost of implicit conversions)
  • Any Scala developer who wants faster Scala out-of-the box, or who uses libraries that leverage the Scalaxy compiler plugin
  • Scala Compiler developers who want to prototype optimizations

Syntax

Compilets are just objects with methods that define match actions.

A match action applies to a pattern and can be :

  • a replacement

    def replace666By777 = replacement(666, 777)
    
  • a compilation warning

    def warn666 = warn("I don't like this number") { 666 }
    
  • a compilation error

    def error666 = fail("Are you satanist ?") { 666 }
    
  • a conditional action, which depends on the actual contents of the AST

    import scalaxy.matchers._ // IntConstant and others
    def additive666(a: Int, b: Int) = 
    	when(a + b)(a, b) {
    		case IntConstant(aValue) :: IntConstant(bValue) :: Nil if (aValue + bValue) == 666 =>
    			error("You sneaky b*****d !")
    		case _ =>
    			warn("Beware of adding integers : might be deprecated in the future")
    	}
    

Of course, you are not restricted to constants : it's possible to add as many variables (and type variables) as you wish to your compilet methods (see examples below).

Examples

  • Simple for loops are notoriously not optimized yet in Scala (the ScalaCL project aimed at fixing that, amongst other things). It's easy to have them rewritten to equivalent while loops :

    def simpleForeachUntil[U](start: Int, end: Int, body: U) = replace(
      for (i <- start until end) 
    	body,
      {
    	var ii = start
    	while (ii < end) {
    	  val i = ii
    	  body
    	  ii = ii + 1  
    	}
      }
    )
    
  • Numeric implicits create lots of NumericOps objects, which are not optimized away by the compiler and might degrate performance. It's easy to get rid of them :

    import math.Numeric.Implicits._
    import Ordering.Implicits._
    
    def plus[T](a: T, b: T)(implicit n: Numeric[T]) = replace(
      a + b, // Expanded to Numeric.Implicits.infixNumericOps[T](a)(n).+(b)
      n.plus(a, b)
    )
    
  • Some Java APIs have been marked as evil for quite a while : you may want to forbid them in your code base :

    def forbidThreadStop(t: Thread) = 
      fail("You must NOT call Thread.stop() !") {
    	t.stop
      }
    

TODO

  • Provide maven shade plugin instructions to use AppendingTransformer for META-INF/scalaxy.compilets
  • More auto-tests : test resulting tree equality w/ macro system ? (what phase are macro executed at ?)
  • Add @Optimization annotation to disable rewrites unless -optimise is set in compiler options
  • Deploy Scalaxy to Sonatype repo
  • Provide usage instructions
  • Add SPI-like META-INF/scalaxy declarations mechanism
  • Create SBT plugin that creates META-INF stuff
  • Add a giter8 template for library designers that want to embed AST rewrites to their libraries
  • Add explicit replacement context declaration, and context limitation
  • Publish case study (Numeric ?)
  • Optimize pattern matching performance with prefix tree (to scale up to many replacements)
Clone this wiki locally