0

Consider the simple trait TypeName

trait TypeName {
    def typeName(): String
}

For a custom class, it is clear how I might specify my implementation for the typeName method.

class Point(val x: Int, val y: Int) extends TypeName {
    def typeName(): String = {
        "Point"
    }   
}

This now allows me to write a function like this

def printlnTypeName[T <: TypeName](value: T) = {
    println(value.typeName());
}

However, if I would like to give a standard library class this functionality. So that I might be able to pass a String, Vector or Array to this printlnTypeName function and print their type name!

How might I do this?

doliphin
  • 752
  • 6
  • 22

1 Answers1

3
  • OOP way would be to introduce wrappers for standard-library classes and make them extend the trait
trait TypeName {
  def typeName(): String
}

class Point(val x: Int, val y: Int) extends TypeName {
  override def typeName(): String = "Point"
}

class StringHolder(value: String) extends TypeName {
  override def typeName(): String = "String"
}

class VectorHolder[A](value: Vector[A]) extends TypeName {
  override def typeName(): String = "Vector"
}

// class VectorHolder[A <: TypeName](value: Vector[A]) extends TypeName {
//   override def typeName(): String = s"Vector[${value.head.typeName()}]"
// }

class ArrayHolder[A](value: Array[A]) extends TypeName {
  override def typeName(): String = "Array"
}

def printlnTypeName[T <: TypeName](value: T) =
  println(value.typeName())
  • FP way would be to introduce a type class (this is a more flexible approach)
// type class
trait TypeName[A] {
  def typeName(): String
}
object TypeName {
  // instances

  implicit val stringTypeName: TypeName[String] = () => "String"

  implicit def vectorTypeName[A](implicit
    aTypeName: TypeName[A]
  ): TypeName[Vector[A]] = () => s"Vector[${aTypeName.typeName()}]"

  implicit def arrayTypeName[A](implicit
    aTypeName: TypeName[A]
  ): TypeName[Array[A]] = () => s"Array[${aTypeName.typeName()}]"
}

class Point(val x: Int, val y: Int)
object Point {
  implicit val pointTypeName: TypeName[Point] = () => "Point"
}

def printlnTypeName[T](value: T)(implicit tTypeName: TypeName[T]) =
  println(tTypeName.typeName())

Some intros to type classes:

https://kubuszok.com/2018/implicits-type-classes-and-extension-methods-part-1/

https://tpolecat.github.io/2013/10/12/typeclass.html https://tpolecat.github.io/2015/04/29/f-bounds.html

https://books.underscore.io/shapeless-guide/shapeless-guide.html#sec:generic:type-classes (chapter 3.1)

https://www.baeldung.com/scala/type-classes

https://docs.scala-lang.org/scala3/book/types-type-classes.html

https://gist.github.com/BalmungSan/c19557030181c0dc36533f3de7d7abf4#typeclasses

  • You can use already existing type classes:
// libraryDependencies += scalaOrganization.value % "scala-reflect" % scalaVersion.value
import scala.reflect.runtime.universe.{TypeTag, typeOf}

def printlnTypeName[T: TypeTag](value: T) =
  println(typeOf[T])

or

// libraryDependencies += "com.chuusai" %% "shapeless" % "2.3.10"
import shapeless.Typeable

def printlnTypeName[T: Typeable](value: T) =
  println(Typeable[T].describe)

(def foo[T: TC](value: T) is a syntax sugar for def foo[T](value: T)(implicit tc: TC[T]))

Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66
  • What is the benefit of writing `stringTypeName` as `implicit val stringTypeName: TypeName[String] = () => "String"`, as opposed to `implicit def ...` – doliphin Dec 21 '22 at 10:13
  • 1
    @doliphin an `implicit def` would create a new instance on each invocation... just like a `def` VS a `val`. `implicit def` are used when you need derivation like `implicit def typeableList[A](implicit ev: Typeable[A]): Typeable[List[A]]`; otherwise is better to just use `implicit val` to cache the instances. – Luis Miguel Mejía Suárez Dec 21 '22 at 14:35
  • @doliphin https://stackoverflow.com/questions/9449474/def-vs-val-vs-lazy-val-evaluation-in-scala – Dmytro Mitin Dec 22 '22 at 06:56