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


#1

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…


#2

I the defn of getInstanceOfMammal() would be important


#3

What do you mean?


#4

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

It’d help me understand the full question.

-steve


#5

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?


#6

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.


#7

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”.


#8

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

#9

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] ^



#10

Fantastic! Just what I need, thanks!!


#11

(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…)


#12

Check out the the scala/scala gitter channel.

Brian Maso


#13

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


#14

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


#15

We can get massages here too?


#16

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


#17

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…


#18

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 {
}