Sunday, March 20, 2022

Scala 3 Type System Notes

Importance of types

Pathological states are unreachable; the state space of the application simply does not include them.

Scala 3 has some new keywords that help metaprogramming. Here are some notes I made as I try to grok them.

New Scala3 Keywords

The inline modifier "tells the compiler that it should inline any invocation of this method at compile-time. If it's not possible, compiler will fail the compilation." Note that the compiler will complain with inline match can only be used in an inline method if you try to put it in a normal match clause.

The inline keyword will partially act like the Scala 2 or C/C++ version of the keyword if the expression is too complex to evaluate at runtime. That is, it will stick the code in at compile time and let it evaluate at runtime. If you don't want this partial evaluation, "you can tell the compiler to avoid complex expression as parameter. This is done by using an inline if" [Scalac blog]. So, by using a def and an if that are both inlined you could, for example have all debug statements removed at compile time.

Note, the parameters to a function can also be inlined.

"erasedValue comes from compiletime package. It's usually used in tandem with inline match. It allows us to match on the expression type, but we cannot access extracted value as that code is executed at compile-time

"constValue comes from compiletime too. It returns the value of a singleton type."

Note that "in Scala 3 you must use a lower-case identifier for a type being extracted from a pattern match." [MichaƂ Sitko's excellent blog post]

The keyword using appears to be the Scala 3 equivalent of implicit in Scala 2. For compilation to succeed, every time you see a using there must be a corresponding given that satisfies its demands.

The keyword with is a synonym for the equals operator. Quoting the Scala Book, this SO answer says:

Because it is common to define an anonymous instance of a trait or class to the right of the equals sign when declaring an alias given, Scala offers a shorthand syntax that replaces the equals sign and the "new ClassName" portion of the alias given with just the keyword with.
The keyword given will make available zero or more functions available in the ether for a corresponding using.

The keyword transparent can be applied to both traits and inline methods. In each case, it's saying the returned type is more specific at compile time. If it cannot be more specific at compile time (say, it depends on a condition that is only known at runtime) then it will fall back to the most general type.

Opaque types seem to basically be tiny types that are very easy to use (see the official documentation). To paraphrase: outside of the module, it is not possible to discover that the opaque type is actually implemented as its alias.

Examples

If we define the natural numbers as:

  trait Nat
  class _0 extends Nat
  class Succ[A <: Nat] extends Nat
  trait <[A <: Nat, B <: Nat]

then the Peano axioms can be represented by:

  given basic[B <: Nat]: <[_0, Succ[B]] with {}
  given inductive[A <: Nat, B <: Nat](using lt: <[A, B]): <[Succ[A], Succ[B]] with {}

(From RockTheJVM)

So, that inductive function allows compilation if it is satisfied in a (potentially) recursive manner until we reach basic. Having these axioms in the ether, we can compile this:

type _1 = Succ[_0]
type _2 = Succ[_1] // Succ[Succ[_0]]
type _3 = Succ[_2] // Succ[Succ[Succ[_0]]]
type _4 = Succ[_3] // Succ[Succ[Succ[Succ[_0]]]]
...

I hope to build on this to make data engineer pipelines easier to debug. I'll expand on this idea in a future post as this has become long enough.

No comments:

Post a Comment