Compacting a class that has many subtypes

I am porting one of my C ++ programs to Scala. This project has hundreds of custom classes in an organized hierarchy. If I seal one of the top level abstract classes, according to Scala rules, I have to put the definition of all subtypes of that class in the same file as the sealed class / trait. For one of my class hierarchies, this would mean putting the definition of about 30 classes into this file.

In my C ++ program, these 30 classes are in their own header and implementation files, making them easier to maintain and read. I'm afraid that if I put the definition of these 30 classes / objects in one file in my Scala application, it will make them difficult to maintain and read.

The reason for sealing a class is so that I can do exhaustive searches when matching patterns across these types. Any help to point me in the right direction regarding Scala class hierarchy organization would be appreciated.

+3


source to share


2 answers


It hurts a little for this in separate classes, but it can be less painful than everything in one huge file. First, you need to make sure that you compile all files together. Then in your file, where you make sure everything is sealed, you do the following:

trait GenericA { def foo: Int }
sealed trait A extends GenericA
case class B() extends A with ImplB {}
case class C() extends A with ImplC {}
...

      

The trick is that everything in the superclass (or maybe an abstract class, not a trait, if you like) goes into GenericA

. But you never use GenericA

in your code, you just use A

. Now you can write a bunch of separate files with each implementation, defined like this:

// File #1
trait ImplB extends GenericA { def foo = 7 }

// File #2
trait ImplC extends GenericA { def foo = 4 }

...

      



You now have your implementations highlighted (at least those parts that can only be expressed in GenericA).

What if you also need case class parameters? No problem - just include them as part of this trait.

// Main file
case class D(i: Int) extends A with ImplD {}

// Separate file
trait ImplD {
  def i: Int
  def foo = i*3
}

      

It's a little extra work as you need to repeat the case class parameters in two places, but in your case it might be worth it.

+3


source


Assuming your classes are case classes that have many methods (which might make your file grow larger), you can try to decouple the definition from the implementation using typeclasses (but this can sometimes degrade compiler performance), for example:

Model.scala

sealed trait A
case class A1(a: Int) extends A
case class A2(a: Int) extends A
case class A3(a: Int, b: Int) extends A
...
case class A1(a: Int) extends A

      

ImplA1.scala

package org.impl
implicit class ImplA1(a: A1) {
   def method1() = a.a + a.a
}

      



ImplA2.scala

package org.impl
implicit class ImplA2(a: A2) {
   def method1() = a.a * 2
}

      

Using:

import org.impl._
val a1 = new A1
a1.method1()

      

+2


source







All Articles