Scala Tricks

This is a collection of Scala tricks that I have found useful. Some of these are Scala 3 specific.

Multiple type parameter lists for a single function

I wanted to split my type parameter list in two, so that one would be inferred, and the other provided by the user. This was especially important for this case as the inferred one had a very complicated type. I found the answer somewhere online, I don’t remember the exact link at the time, but the approach was attributed to Miles Sabin here.

trait Foo[A]:
  def apply[B](a: A, b: B): (A, B) = (a, b)
  def apply[B, C](a: A, b: B, c: C): (A, B, C) = (a, b, c)

new Foo[Int] {}.apply[String](1, "2") // : Tuple2[Int, String]
new Foo[Int] {}.apply[String, Int](1, "2", 3) // : Tuple3[Int, String, Int]

def foo[A] = new Foo[A] {}

foo(1, "2") // : Tuple2[Int, String]
foo(1, "2", 3) // : Tuple3[Int, String, Int]
foo[Int][String, Int](1, "2", 3) // : Tuple3[Int, String, Int]

// Alternative:
object Foo:
  def apply[A] = new Foo[A] {}

Foo(1, "2") // : Tuple2[Int, String]
Foo(1, "2", 3) // : Tuple3[Int, String, Int]
Foo[Int][String, Int](1, "2", 3) // : Tuple3[Int, String, Int]

Match Types and Tuples

The new Scala 3 tuple types are both fun to use, and I have found them very useful. The tuple type is constructed much like a cons-list, and we can deconstruct it similarly using match types.

The Scala standard library already provides very useful match types for working with tuples. Here I will paste the Head and the Map match types. They can be accessed by writing Tuple.Head and Tuple.Map respectively. I simply refer to the standard library, the examples there are very useful for seeing match types in action.

/** Type of the head of a tuple */
  type Head[X <: NonEmptyTuple] = X match {
    case x *: _ => x
  }

/** Converts a tuple `(T1, ..., Tn)` to `(F[T1], ..., F[Tn])` */
  type Map[Tup <: Tuple, F[_ <: Union[Tup]]] <: Tuple = Tup match {
    case EmptyTuple => EmptyTuple
    case h *: t => F[h] *: Map[t, F]
  }

Type Predicates and Implicit Evidence

Another thing that I have been playing with is using match types and implicit evidence for constructing something which I call type predicates (I am sure there is a better name for this). I have been using this as an internal API, trying to restrict certain type parameters to certain properties, for example to ensure that a type parameter is not Nothing. I am not 100% sure if this works, but so far have not had any issues with it.

object Predicates:
  /** If type T is not Nothing */
  type NotNothing[T] = true =:= T match
      case Nothing => false
      case _       => true

  object NotNothing:
    /** implcitNotFound message */
    inline val MSG = "Type T is ${T}, it is required to NOT be Nothing"

The example usage is to add it as a guard on a function, to ensure that the type parameter is not Nothing.

import scala.annotation.implicitNotFound
import Predicates.*

def foo[T]()(using @implicitNotFound(NotNothing.MSG) ev: NotNothing[T]): Unit =
  print("foo")

foo[Int]()

// compiler error: Type T is Any, it is required to NOT be Nothing
// foo() 

// compiler error: Type T is Nothing, it is required to NOT be Nothing
// foo[Nothing]() 

Type-Safe Config

The idea of Type Predicates is taken to the extreme with a type-safe config that I built (mostly for fun, not much profit), using Heterogeneous Maps. This type of pattern is described well in other sources, but I forgot the links, it should suffice to google for “Heterogeneous Maps in Scala 3”. Check out Config.scala.

Scala Tricks by Jonas Spenger, 15 Jan 2023, last updated on 15 Jan 2023.
Tags: Scala, 3.
There are likely some errors I have missed, please contact me if you find any.
Check out more posts in the blog archive.