Is there a way to prevent a method on a trait from being called on a specific subclass?

Say I have:

trait Animal
def create(e: Animal)

trait Mammal extends Animal
def create(arg1: String, arg2: String)

Is there any way to prevent this from compiling?

val mammal = getInstanceOfMammal()
mammal.create(e) // Want this to not compile

I’m not interested in answers telling me my design is wrong, I’m only interested if anyone has a clever solution to this, using some of Scala’s magic…

I the defn of getInstanceOfMammal() would be important

What do you mean?

As an example is it merely returning an intance of Mammal?

It’d help me understand the full question.

-steve

The thing is I want any call to Mammal.create(e: Animal) to not compile, to enforce the user to call create(String, String) instead. I know it violates lots of stuff, but I still wonder, is it doable?

No, it is not doable because every Mammal is also an Animal. You can always “forget” you have a Mammal and treat it as an Animal and Scala will have no way to know the difference (because there isn’t any). This is a property of subtyping in general, not a Scala thing specifically.

I know all this.
But I’m still wondering if it’s possible, using som “typelevel magic”, to circumvent it for a specific overloaded method. Some mechanisme saying “Yea, I know I’m supposed to support abstract method this and that, but this implementation is different so throw an error at compile-time if someone calls this declared method on this specific subtype”.

If you really want to…

scala> import scala.annotation.compileTimeOnly
import scala.annotation.compileTimeOnly

scala> class Mammal extends Animal {
     |   @compileTimeOnly("don't call this!")
     |   final def create(a: Animal) = ???
     | }
defined class Mammal

scala> new Mammal().create(new Mammal)
                    ^
       error: don't call this!

But be warned:

scala> (new Mammal(): Animal).create(new Mammal)
scala.NotImplementedError: an implementation is missing
  at scala.Predef$.$qmark$qmark$qmark(Predef.scala:344)
  at Mammal.create(<console>:3)
  at Mammal.create(<console>:1)
  ... 38 elided

If your are interested in a method that doesn’t compile whit a specific type you can also use shapeless like the following.


import shapeless._

sealed trait Animal
final case class Mammal() extends Animal
final case class NonMammal() extends Animal

def create[A <: Animal](e: A)(implicit ev: A =:!= Mammal): Unit = ()

val mammal = Mammal()
val nonMammal = NonMammal()

create(nonMammal)

//create(mammal)
//
// [error] match expected type Mammal =:!= Mammal
// [error] create(mammal)
// [error] ^


Fantastic! Just what I need, thanks!!

(BTW; This forum is horrible. My reply is shown below the wrong message, and interaction with email is a joke. Whish scala-users moved back to standard email-list, ie. mailman… Yes, I’m over 40…)

Check out the the scala/scala gitter channel.

Brian Maso

The email interaction is terrible, i use it only to get notified of massages, but the forum itself is decent IMO.

Gitter is IMO just as horrible, and the search-facilities really suck…

We can get massages here too?

Yes, but create(nonMammal: Animal) works fine.

Yes, absolutely correct and potentially harmful in with subtyping contex , as you said

You can always “forget” you have a Mammal and treat it as an Animal

as well the static annotation is potentially harmful.

In the following example everything compiles fine.

  sealed trait Animal {
    def aMethod(): Int = 1
  }
  final case class Mammal() extends Animal {
     @compileTimeOnly("don't call this!")
     override def aMethod(): Int = ???
  }

  def create[A <: Animal](a: A)(implicit ev: A =:!= Mammal): Unit = 
    println(a.aMethod())

  val mammal = Mammal()

The following thwos
Exception in thread "main" scala.NotImplementedError: an implementation is missing

  (mammal: Animal).aMethod()

As well this

  create(mammal: Animal) // runtime error

Now it’s problematic having a List[Animal] you don’t know if it’s safe to call aMethod() or create on the elements…

Not sure your exact use case here, but this may be where you want to break Animal up into different traits.

For instance

trait Animal {}

trait AnimalWithCreate {
   def create(e: Animal)
}

// Does not have the ability to create.
trait Mammal extends Animal {
}