Metaprogramming in Scala: A self-transforming code

Reading Time: 4 minutes

Meta programming is a popular technique from 1970’s and 1980’s which used languages like LISP to enable applications to process code for artificial intelligence based applications. When a programming language is it’s own meta-language then it is called as reflection. Reflection is one of the important feature for any programming language to facilitate meta programming. Meta programming moves computations from run-time to compile-time thereby enabling self-modifying code. Hence, a program is designed in such a way that it can read, analyse or transform other programs or itself while it is running. This style of programming falls under Generic programming paradigm where the programming language itself is a first-class datatype.

This metaprogramming is exercised in various programming languages for various purposes. In Scala it is used as macro systems, muti-stage programming (runtime staging) etc.

Meta programming in Scala

Meta programming in Scala introduces fundamental features like:

  1. Macros: built on two fundamental operations: quotation (via. as ‘{…}) and splicing (via. as ${ … }). Along with inline, these two abstractions allow to construct program code pro-grammatically.
  2. inline: A new modifier which guarantees definition will be inlined at point of use. It reduces overhead of function call and values access.
  3. Compile-time ops: helper functions that provides support for compile time operations like constValue and constValueOpt
  4. Runtime Staging: To make code generation depend on run time data, staging lets code construct new code at runtime.
  5. Reflection
  6. TASTy Inspection: Typed Abstract Syntax Tree allows to load files and analyse their content in tree structure.

Adding these new meta programming capabilities adds enormous benefits and privileges for eliminating the boilerplate code and improving the overall performance of applications. With meta programming developers in scala can leverage their applications performance and remove all redundant & boilerplate code with use of these features.

Trade-Offs: Macros over Functions

Execution Time: With macros we can make execution comparatively faster. As during processing a macro is expanded and replaced by its definition each time its used. On the other hand function definition occurs only once irrespective of number of times its called. Macros might increase code s lines of code but don’t have overhead associated with function calls.

Clean Code with Macros

Repeated Code: Even though Scala syntax is concise that avoids boilerplate code that occurs in other JVM programming language. But still there are scenarios where developers might end up writing repetitive code and which can’t be refactored further for reuse. With Scala macros we can keep code clean and maintainable.

Meta programming Features Snippets

Inlined Method Example

inline def repeat(s: String, count: Int): String =

  inline count match

    case 0 => ""

    case _ => s + repeat(s, count-1)

Macros Example

import scala.quoted.*

// Note the quote '{...} and the argument types

private def failImpl[T](

      predicate: Expr[Boolean], message: Expr[String],

      block: Expr[T], beforeAfter: Expr[String])(

      using Quotes): Expr[String] =

    '{ throw InvariantFailure(  

      s"""FAILURE! predicate "${${showExpr(predicate)}}" """

      + s"""failed ${$beforeAfter} evaluation of block:"""

      + s""" "${${showExpr(block)}}". Message = "${$message}". """)


private def showExpr[T](expr: Expr[T])(using Quotes): Expr[String] =
val code: String = 


TASTy Inspection Example

<Sample .tasty file>

import scala.quoted.*

import scala.tasty.inspector.*

class MyInspector extends Inspector:

   def inspect(using Quotes)(tastys: List[Tasty[quotes.type]]): Unit =

      import quotes.reflect.*

      for tasty <- tastys do

         val tree = tasty.ast

         // Your code here

Consumer of above .tasty file

object Test:

   def main(args: Array[String]): Unit =

      val tastyFiles = List("sample.tasty")

      TastyInspector.inspectTastyFiles(tastyFiles)(new MyInspector)

Compile-time ops Example

/* constValue - function to produce constant value represented by a type, or 

a compile time error if the type is not a constant type. constValueOpt is  

same as constValue, however it returns Option[T] to handle where a value is 

not present. */

import scala.compiletime.constValue


transparent inline def toIntConst[N]: Int =

  inline constValue[N] match

    case 0        => 0

    case _: S[n1] => 1 + toIntConst[n1]

inline val constTwo = toIntConst[2]

Meta programming Applications

Program transformation systems can be helpful to build:

  • Test coverage and profiling tools
  •  Code generation & completion tools
  •  Automated Refactoring tools
  •  Language migration tools
  •  Tools to re-architecture/re-shape applications
  •  Build Domain Specific Languages via. Metaprogramming
  •  Project Templates
  • Dynamic Dispatch
  • Reflection
  • Aspect Oriented Programming (to resolve issues w.r.t logging, transaction management etc.)
  •  GUI code generation
  •  Compilers & Interpreters implementation
  •  Frameworks
  •  ORM in dynamic language


In statically typed languages like Java and Scala, meta programming is more constrained and is very less common. But it’s still useful for solving many advance real-time design problems. With more effort to separate compile-time versus runtime manipulation. Also, it gives more flexibility and configuration at runtime. Overall program can become more expressive and flexible with these techniques.

Written by 

Karuna Puri is Tech. Lead at Knoldus Inc. with 8+ years of experience. She is backend developer with expertise in Functional Programming language - Scala. She is also well versed on cloud front with AWS. She is a tech. enthusiastic with interests in other domains - Data Mining and Machine Learning.

Leave a Reply