In this article we combine opaque type alias hierarchies with implicit resolutions to do calculations. Scala 3 brings several language enhancements that give programmers even better control over types . One of them is opaque keyword.
For a deeper look what opaque is please visit opaques page at dotty.epfl.ch. Let’s continue with an example and define some types:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
package types {
  // base type aliases  
  opaque type StringBased = String
  opaque type IntBased = Int
  
  // instantiable type aliases 
  opaque type TString1 <: StringBased = String
  opaque type TString2 <: StringBased = String
  opaque type TInt <: IntBased = Int
  
  // constructor functions  
  def TString1(p: String): TString1 = p
  def TString2(p: String): TString2 = p
  def TInt(p: Int): TInt = p
 }

Let’s explain these things a bit. We have declared several new type aliases in types scope (in our case scope is package types). Inside of the types scope all of these aliases works as ordinary type aliases, so we can easily create “constructor” functions. Also we declared “base aliases” named StringBased and IntBased. So expression: TString1 <: StringBased we understand that TString1 is subtype of StringBased. Let’s imagine we need do a calculation with our new types, let us call it Transformer .

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
package transformers {
  import types._

  // transformer declaration   
  type Transformer[T] = (T => String)

  // default implementation based on our "base types"
  given stringTransformer[S<:StringBased]:Transformer[S] = (p:S) =>s"string transformer: ${p}"
  given intTransformer[T<:IntBased]:Transformer[T] = (p:T) => s"int transformer: ${p}"
}

In package transformers we defined also basic transformers stringTransformer, and intTransformer. These functions return input parameter prefixed by some text. given is new keyword introduced in Scala 3, it is substitution for constructs like implicit val or implicit def.

Let’s put everything together into a running example:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
object Main {
  import types._
  import transformers._
  import transformers.{given} // import all implicit definitions

   //  let specialize transformer 
  given tString2Transformer:Transformer[TString2] = (p:TString2) => s"tString2 transformer: ${p}"
 
  // printer
  def printWithTransformer[T:Transformer](p:T) = {
    val w = summon[Transformer[T]]
    println(w(p))
  }

  def main(args: Array[String]): Unit = {
    val m1 = TString1("Hello")
    printWithTransformer(m1)
    val m2 =  TString2("Hello2")
    printWithTransformer(m2)
    val l = TInt(42)
    printWithTransformer(l)
  }    

Run example here. result should be

string transformer: Hello
TString2 transformer: Hello2
int transformer: 42

We can easily use type hierarchies for specialization, see : given String2Transformer:Transformer[TString2] ... . And of course, one can do more abstraction with union or intersection types used together with opaque type construct.