Scala
Switch branches/tags
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Failed to load latest commit information.
project
src
.gitignore
.scalafmt.conf
.travis.yml
LICENSE
README.md
build.sbt
deploy.sbt.disabled
version.sbt

README.md

template.scala

Join the chat at https://gitter.im/ThoughtWorksInc/template.scala Build Status Latest version

template.scala is a library for creating inline functions, similar to C++ templates.

Usage

scalaVersion := "2.12.1" // or "2.11.8"

libraryDependencies += "com.thoughtworks.template" %% "template" % "latest.release" % Provided

addCompilerPlugin("org.scalameta" % "paradise" % "3.0.0-M7" cross CrossVersion.full)

A template function is created with a @template annotation.

@template
def max(x: Any, y: Any) = {
  if (x > y) x else y
}

Unlike normal functions, a template function will not be type-checked until using it. Thus it does not raise a type error on x > y because the real types of x and y have not been determined.

val i: Int = max(1, 2)
val d: Double = max(8.0, 0.5)

The max function will be type-checkd and inlined whenever being invoked.

If the type of x does not support > method, it does not compile:

val s: Symbol = max('foo, 'bar)
<macro>:1: value > is not a member of Symbol
def max(x: Any, y: Any) = if (x > y) x else y
                                ^

Side effects

By default, the side effects in arguments of @template functions will be evaluated before the execution of the function body. For example:

max({
  println("x = 1")
  1
}, {
  println("y = 2")
  2
})

The output is

x = 1
y = 2

However, you can use call-by-name parameter to force the side effects re-evaluate whenever the argument is referenced. For example:

@template
def callByNameMax(x: => Any, y: => Any) = {
  if (x > y) x else y
}

callByNameMax({
  println("x = 1")
  1
}, {
  println("y = 2")
  2
})

The output is

x = 1
y = 2
y = 2

Recursive template functions

Template functions can be recursive, as long as the number of calls are finite and can be determined at compile-time.

The following code creates a heterogeneous list.

sealed trait HList {

  final def ::(head: Any): head.type :: this.type = {
    new (head.type :: this.type)(head, this)
  }

}

case object HNil extends HList

final case class ::[Head, Tail <: HList](head: Head, tail: Tail) extends HList {
  def apply(i: 0): head.type = {
    head
  }

  @template
  def apply(i: Int with Singleton): Any = {
    tail(i - 1)
  }

}

Then you can index elements in the HList via template function apply.

val hlist = "foo" :: 1 :: false :: HNil

val s: String = hlist(0)
val i: Int = hlist(1)
val b: Boolean = hlist(2)

hlist(3) // Compile error

Note that the above HList example need TypeLevel Scala and -Yliteral-types flag.

Limitations

  • By default, @template functions are inline, not sharing similar implementations like C++ templates.
  • @template functions do not support type parameters. You can create type aliases instead. See the test case for example.
  • Recursive @template functions must be resolved at compile-time.