3

Given a concrete class Animal, how do I define a function that only takes a subclass of Animal?

In typical examples like this Animal is a trait so defining [A <: Animal] implies that you already pass in a subclass of Animal. However, in a scenario like below where Animal is concrete, can I exclude that as being an allowed type?

I'm working with existing generated code, and this is just a generalized example of the problem. Therefore the implication is that I can't make Animal (or the equivalent) into a trait.

See below for an example:

class Animal {
  def name: String = "General Animal"
}

class Dog extends Animal {
  override def name: String = "Dog"
}

// How do I limit A to be a subtype of Animal (excluding Animal itself)?
class SpecificAnimalContainer[A <: Animal](a: A) {
  def specificAnimal: A = a
}

val dogContainer = new SpecificAnimalContainer[Dog](new Dog)

// I do not want this to be able to compile.
val animalContainer = new SpecificAnimalContainer[Animal](new Animal)
jon_wu
  • 1,113
  • 11
  • 26
  • 1
    Make animal a `trait`. Then you cannot instantiate `Animal`. Problem solved. In reality, there are no *animals*, just dogs and cats and so on. – ziggystar Feb 02 '18 at 21:07
  • @ziggystar huh? What hinders you to instantiate a trait? O_o? You're instantiating `Function[X, Y]` thousand times every day... – Andrey Tyukin Feb 02 '18 at 22:07
  • @ziggystar I mentioned that "I'm working with existing generated code", therefore I can't make `Animal` into a trait – jon_wu Feb 02 '18 at 22:59
  • "I can't make Animal into a trait" in no way follows from "I'm working with existing generated code", because "generated code" could mean anything. Is it you who generates it? Then tweak the generator. Or add some post-generate pre-compile process-sources step in your build. That's a completely different question. And there were at least two people who assumed that you can declare your types however you want (as a trait, for example), so I don't see why my answer is worth a downvote. I don't like being downvoted for not being able to catch up with moving-target-questions, sorry. – Andrey Tyukin Feb 03 '18 at 01:04
  • Sorry if the question was confusing. This is why I added clarification. I'm happy to accept a different response even if it's just one explaining why perhaps this isn't possible in Scala. To be fair, you still didn't answer the question directly. While your solution made it impossible to instantiate the `Animal` class, it did not specifically show me how I could me a method that would only accept a strict subtype of `Animal` (excluding `Animal` itself). – jon_wu Feb 03 '18 at 02:47

3 Answers3

5

Using shapeless you can write:

import shapeless._

class SpecificAnimalContainer[A <: Animal](a: A)(implicit ev: A =:!= Animal) {
  def specificAnimal: A = a
}

//  val animalContainer = new SpecificAnimalContainer[Animal](new Animal)// doesn't compile

Otherwise you can implement similar type for implicit yourself.

Type constraint for type inequality in scala

Enforce type difference

How can I have a negation type in Scala?

Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66
1

In Scala 3 this is easy:

import scala.util.NotGiven

class Animal:
  def name: String = "General Animal"

class Dog extends Animal:
  override def name: String = "Dog"

class SpecificAnimalContainer[A <: Animal : [A] =>> NotGiven[A =:= Animal]](a: A):
  def specificAnimal: A = a

val dogContainer = new SpecificAnimalContainer[Dog](new Dog)

// doesn't compile
val animalContainer = new SpecificAnimalContainer[Animal](new Animal)
Martin Ring
  • 5,404
  • 24
  • 47
0

It's a bit unclear what you're trying to achieve, but your problem looks exactly like a book example from Scala documentation at https://docs.scala-lang.org/tour/upper-type-bounds.html

abstract class Pet extends Animal {}

class PetContainer[P <: Pet](p: P) {
  def pet: P = p
}

class Lion extends Animal {
  override def name: String = "Lion"
}

//  val lionContainer = new PetContainer[Lion](new Lion)
//                         ^this would not compile

Hope this helps

ZakukaZ
  • 528
  • 6
  • 11
  • Perhaps this answer would be better as comment. Yes, my code is modeled after that, but there's one big difference. My the type I'm using in the upper bounds is not `abstract` and I want to make sure that no instance of it can be passed in. In my example, the key is "How do I limit A to be a subtype of Animal (excluding Animal itself)?" However, it looks like the other answer by @Dmytro answers this. – jon_wu Feb 06 '18 at 21:24