Awesome
template.scala <a href="http://thoughtworks.com/"><img align="right" src="https://www.thoughtworks.com/imgs/tw-logo.png" title="ThoughtWorks" height="15"/></a>
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.