Search code examples
scalaimplicit-conversionimplicittype-constraints

Scala: Extra functionality in a class, based on presence of constructor arguments


Let's say I have a "Base" class, and I want to add a "whoa" val to it.

But, the "whoa" val should ONLY be available if I provided a certain constructor argument (in this case, that argument is a function).

So, I created an extra "WhoaClass", containing the method "whoa", which "Base" can be implicitly converted to if need be. I just have to check that the constructor argument "func" is present (by checking that its type is not Null, which necessitates the ugly hack near the bottom)

My question is: Is there a better way to do this? Especially in terms of readability, and doing away with the ugly hack. I'm trying to do this still using constructor arguments and not some kind of builder. I'd really like the inline the "whoa" function into the Base class, instead of having it dangling in another class, which isn't very clear.

object WhoaTest {
    // the type of function I want to take
    type Func = Int => Option[Long]

    // this is the base class that takes a function
    case class Base[A <: Func](func: A = null) {}

    // this is the class with the extra method
    class WhoaClass(a: Long) {
        val whoa = "Whoa" + a.toString
    }

    // the implicit method to convert the base object into a WhoaClass with a whoa method
    implicit def enableWhoa[A <: Func](a: Base[A])
        (implicit ev: A =!= Null) // only enable this implicit if the function is present
        = new WhoaClass(a.func(50).get)

    // the hack for extra scala type checking
    trait =!=[A, B]
    implicit def neq[A, B] : A =!= B = null

    // Causes an ambiguity if the two types are the same
    implicit def neqAmbig1[A] : A =!= A = null
    implicit def neqAmbig2[A] : A =!= A = null
}

Solution

  • First, null testing is a quite 'unusual' habit in Scala. Then I think you'd better have a base trait and factory functions in companion object.

    trait Base { /* common code, unconditionnally */ }
    
    object Base {
      def apply(): Base = new Base {} // factory when no arg
    
      def apply(a: Long): Base = new Base {
        overide lazy val toString = s"Whoa: $a"
      }
    }
    
    // Then can do ...
    val base1: Base = Base() // first apply factory
    val base2: Base = Base(1L) // second factory