Introducing Transparent Traits in Scala 3

Reading Time: 2 minutes

I am learning Scala 3 and as I go along, I want to share about it with our community. In this quest, I had written an article a few days back on Trait Parameters in Scala 3. Check it out! In this article, I am going to write about a new feature of Scala 3, called Transparent Traits. Firstly, we will look into the problem that this feature is trying to solve and then the feature itself.

A brief history

In Scala, a trait can be used to represent the type of a val or a def. Also, traits are reusable components which help in code reuse. Looking at an example here –

package com.template
import scala.util.Random
object Trading extends App {
val Sell = "sell"
val Buy = "buy"
trait Id {
def id: Int
}
trait Order {
def quantity: Int
}
case class BuyOrder(id: Int, quantity: Int) extends Order with Id
case class SellOrder(id: Int, quantity: Int) extends Order with Id
def mkOrder(side: String, qty: Int) =
if (side.equals(Sell)) SellOrder(mkId, qty) else BuyOrder(mkId, qty)
def mkId: Int = Random.nextInt()
println(mkOrder("buy", 100))
}
view raw Trading hosted with ❤ by GitHub

In this code, the inferred type of mkOrder is Order with Id with Product.

Ignoring how Product is inferred for a moment, do we need Id in the type in this case? Here, it simply is as an implementation detail where we want every class extending Id trait to give a concrete implementation for its method id. Therefore, Id is not necessarily representing a type here.

Scala 3 Transparent Traits

In Scala 3, it is possible to suppress traits while inferring the type. We need to mark such traits as transparent. Re-writing the above code using Scala 3.

import scala.util.Random
object TransparentTrait {
val Sell = "sell"
val Buy = "buy"
transparent trait Id {
def id: Int
}
trait Order {
def quantity: Int
}
case class BuyOrder(id: Int, quantity: Int) extends Order, Id
case class SellOrder(id: Int, quantity: Int) extends Order, Id
def mkOrder(side: String, qty: Int) =
if (side.equals(Sell)) SellOrder(mkId, qty) else BuyOrder(mkId, qty)
def mkId: Int = Random.nextInt()
def test: Unit = println(mkOrder("sell", 100))
}

In this piece of code, as we have marked the Id trait as transparent, it is suppressed while inferring the type of mkOrder. This is it. Do this much and have no complex type inferences.

Product Trait – As you had seen with the Scala 2 example, the inferred type was Order with Id with Product. Product is a trait in Scala which is inherited to every case class automatically and hence was part of the inferred type. In Scala 3, Product trait has been marked as transparent. So therefore in my example using Scala 3, the inferred type is simply Order because I have marked Id as transparent and Product is automatically treated as transparent.

Rules for inference

Following are the rules for dropping transparent traits while inferring the types. (As stated in official documentation)

  • When inferring a type of a type variable, or the type of a val, or the return type of a def,
  • where that type is not higher-kinded,
  • and where B is its known upper bound or Any if none exists:
  • If the type inferred so far is of the form T1 & ... & Tn where n >= 1, replace the maximal number of transparent Tis by Any, while ensuring that the resulting type is still a subtype of the bound B.
  • However, do not perform this widening if all transparent traits Ti can get replaced in that way.

Transparent Scala 3 traits.

  • scala.Product
  • java.lang.Serializable
  • java.lang.Comparable

This brings us to the end of this blog. Hope you got something to learn from this. Please feel free to drop us a message in case of any queries and checkout other blogs written by my fellow engineers here.

References
http://dotty.epfl.ch/docs/reference/other-new-features/transparent-traits.html

Knoldus-blog-footer-image