3

I'd like to create an abstract class, and be able to add members to it that reference attributes of the implementing class's companion object. Something like this (Scala pseudocode):

abstract class Fruit(cultivar: String) {
    // How do I reference the implementing class's companion object here?
    def isTastyCultivar(): Boolean = Fruit.tastyCultivars.contains(cultivar)
}

// how do I implement what I am thinking of as "the abstract companion object"
abstract object Fruit {
    val tastyCultivars: Set[String]  // must be implemented
                                     // by the concrete object
}

class Apple(cultivar: String) extends Fruit(cultivar) {

}

object Apple extends Fruit{ // presumably this is not correct; 
                            // it needs to extend the FruitObject
                            // or whatever it is
    val tastyCultivars: Set[String] = Set("Red Delicious", "Granny Smith")
}

class Tomato(cultivar: String) extends Fruit(cultivar) {

}

object Tomato extends Fruit{
    val tastyCultivars = Set("Roma")
}

val a1 = new Apple("Red Delicious")
val a2 = new Apple("Honeycrisp")

a1.isTastyCultivar()  // should return true
a2.isTastyCultivar()  // should return false

val t1 = new Tomato("Roma")
val t2 = new Tomato("San Marzano")

t1.isTastyCultivar() // should return true
t2.isTastyCultivar() // should return false

Sorry if this is a dumb question, or if asked previously (I'm not confident in how to word this question so I couldn't easily search for it). Thanks in advance!

  • Your problem is how to force the creation of these `isTastyCultivar` methods? – Yuval Itzchakov Jul 04 '17 at 20:00
  • Two problems; in the isTastyCultivar, how do I reference the concrete object (what do I replace Fruit.tastyCultivars with) and two, how do I force the declaration of tastyCultivars on the two concrete objects – Jonathan Cole Jul 04 '17 at 20:02
  • In other words I specifically do not want to have to redefine the isTastyCultivar() method on each concrete class – Jonathan Cole Jul 04 '17 at 20:03

4 Answers4

3

One possible solution is to use the type class pattern. We have our domain model (or algebra) via ADTs:

sealed trait Fruit
case class Apple() extends Fruit
case class Orange() extends Fruit

We have our type class, which defines the structure we want to supply:

trait TastyCultivarSupplier[T <: Fruit] {
  def tastyCultivars: Set[String]
}

And now each type which has tasty cultivars will need to implement the type class in order to provide them. One possible way to do this is to implement the typeclass inside the companion object:

object Apple {
  implicit def appleTastyCultivars = new TastyCultivarSupplier[Apple] {
    override def tastyCultivars: Set[String] = Set("Yummy stuff")
  }
}

Inside the consumer, or the type which wants to get the tasty cultivars, we require an implicit evidence of a TastyCultivarSupplier:

class TastyCultivarConsumer {
  def isTasty[T: TastyCultivarSupplier](name: String): Boolean =
    implicitly[TastyCultivarSupplier[T]].tastyCultivars.contains(name)
}

When isTasty is invoked, it will need to have one of the suppliers in scope, otherwise a compile time error will occur:

def main(args: Array[String]): Unit = {
  println(new TastyCultivarConsumer().isTasty("Yummy stuff"))
}

Will give us:

Error:(33, 48) could not find implicit value for evidence parameter
of type TastyCultivarSupplier[T]
    println(new TastyCultivarConsumer().isTasty("Yummy stuff"))

To fix this, we import the supplier we want:

def main(args: Array[String]): Unit = {
  import Apple._
  println(new TastyCultivarConsumer().isTasty("Yummy stuff"))
}

And now our code compiles. Note that the implementer isn't forced to write the evidence inside the companion object, he is free to do so anywhere he wants, as long as it's in scope for the compiler to find.

Yuval Itzchakov
  • 146,575
  • 32
  • 257
  • 321
  • Thanks very much for the detailed answer. But it is still not clear to me how I make the Apple class into a TastyCultivarConsumer(). In other words what I want to end up with in main is instead println(new Apple("Yummy stuff).isTasty()) – Jonathan Cole Jul 04 '17 at 21:49
2

A simple solution (the one Scala Collections use): just add a method returning the companion to the class.

trait FruitCompanion {
  val tastyCultivars: Set[String]
}

abstract class Fruit(cultivar: String) {
  def companion: FruitCompanion
  def isTastyCultivar(): Boolean = companion.tastyCultivars.contains(cultivar)
}

class Apple(cultivar: String) extends Fruit(cultivar) {
  def companion = Apple
}

object Apple extends FruitCompanion {
  val tastyCultivars: Set[String] = Set("Red Delicious", "Granny Smith")
}

Note that you can't enforce that the companion actually returns the companion object, but you don't really need it.

Alexey Romanov
  • 167,066
  • 35
  • 309
  • 487
  • 1
    Perfect that's exactly what I was looking for, thank you. I'd upvote you but my account is too new, not enough rep to upvote things – Jonathan Cole Jul 05 '17 at 14:18
0

It is not possible to do what you are asking.

You are asking about overriding members of object. objects in scala are equivalent of static classes in Java with all members being static. Companion object with its class would be a class with both static and none static members in Java. You can't override them. You can't do it in Java and you can't do it in Scala.

Alternative solution:

Just define tastyCultivars in class.

abstract class Fruit(cultivar: String) {
  val tastyCultivars: Set[String]
  def isTastyCultivar(): Boolean = tastyCultivars.contains(cultivar)
}

class Apple(cultivar: String) extends Fruit(cultivar) {

    val tastyCultivars: Set[String] = Set("Red Delicious", "Granny Smith")
}

class Tomato(cultivar: String) extends Fruit(cultivar) {

    val tastyCultivars = Set("Roma")
}

val a1 = new Apple("Red Delicious")
val a2 = new Apple("Honeycrisp")

println("Should return true, is " + a1.isTastyCultivar())
println("Should return false, is " +a2.isTastyCultivar())

val t1 = new Tomato("Roma")
val t2 = new Tomato("San Marzano")

println("Should return true, is " +t1.isTastyCultivar())
println("Should return false, is " +t2.isTastyCultivar())

Output:

$ scala fruit.scala 
Should return true, is true
Should return false, is false
Should return true, is true
Should return false, is false
Gustek
  • 3,680
  • 2
  • 22
  • 36
  • It's very much possible, unlike in Java. Overriding members of `object`s isn't, but only because extending them isn't, and it isn't really what he's asking about. – Alexey Romanov Jul 05 '17 at 09:31
0

Many thanks to Yuval for teaching me about typeclasses; I have written a solution using his technique which satisfies all my original criteria:

// Fruit instance members
abstract class FruitClass(cultivar: String) {
    def getCultivar: String = cultivar
}

// Obviously not really an object but serves the purpose of
// the thing I envisioned as the "abstract object" which is not a thing
// I.e., a place to put fruit static members
trait FruitObject[A <: FruitClass] {
    // Any subclass of fruit must (statically) specify their set of tasty cultivars...
    val tastyCultivars: Set[String]
    // ...but they can inherit the common implementation of isTastyCultivar()
    def isTastyCultivar(x: A): Boolean = tastyCultivars contains x.getCultivar
}

// Subclass #1: Apples
class Apple(cultivar: String) extends FruitClass(cultivar)
implicit object AppleIsFruit extends FruitObject[Apple] {
    val tastyCultivars = Set("Red Delicious", "Granny Smith")
}

// Subclass #2: Tomatoes
class Tomato(cultivar: String) extends FruitClass(cultivar)
implicit object TomatoIsFruit extends FruitObject[Tomato] {
    val tastyCultivars = Set("Roma")
}

def isTastyCultivar[A <: FruitClass: FruitObject](thing: A) =
    implicitly[FruitObject[A]].isTastyCultivar(thing)

isTastyCultivar(new Apple("Red Delicious"))    // true
isTastyCultivar(new Apple("Honeycrisp"))    // false
isTastyCultivar(new Tomato("Roma"))            // true
isTastyCultivar(new Tomato("San Marzano"))    // false

Gustek is quite right that object members cannot be overridden but this method seems to achieve the same effect without actually using a companion object for Fruit to declare static members.