1
votes

Dear Scala developers,

Context

I have two Scala projects, called core and infra. The core project contains trait definitions, while infra contains the concrete implementations of those. Therefore, infra depends on core.

In the core project, I have this trait that uses cake pattern.

trait GetSectionsOfTypeComp {
  def sectionsOfType[SectionType <: SoftwareSection](implicit evidence: GetSectionsOfType[SectionType]): GetSectionsOfType[SectionType]

  trait GetSectionsOfType[SectionType <: SoftwareSection] {
    def getAll(dataStream: InputStream): Seq[SectionType]
  }
}

where SoftwareSection is a trait with one child:

sealed trait SoftwareSection {
  def name: String
  def rawAddress: SoftwareAddress
  def rawSize: Long
}

case class CodeSoftwareSection(name: String, rawAddress: SoftwareAddress, rawSize: Long) extends SoftwareSection

In the infra project, I would like to segregate the logic depending on the type parameter of the method GetSectionsOfType.sectionsOfType. Therefore, I use type classes:

trait PeParserGetSectionsOfCodeComp extends GetSectionsOfTypeComp {
  implicit val foo: GetSectionsOfType[CodeSoftwareSection] = GetSectionOfCode

  override def sectionsOfType[SectionType <: SoftwareSection](implicit evidence: GetSectionsOfType[SectionType]): GetSectionsOfType[SectionType] = evidence

  implicit object GetSectionOfCode extends GetSectionsOfType[CodeSoftwareSection] {
    override def getAll(dataStream: InputStream): Seq[CodeSoftwareSection] = Seq(CodeSoftwareSection("code", SoftwareAddress(0), 0))
  }
}

trait PeParserGetSectionsOfDataComp extends GetSectionsOfTypeComp {
  implicit val bar: GetSectionsOfType[DataSoftwareSection] = GetSectionOfData

  override def sectionsOfType[SectionType <: SoftwareSection](implicit evidence: GetSectionsOfType[SectionType]): GetSectionsOfType[SectionType] = evidence

  implicit object GetSectionOfData extends GetSectionsOfType[DataSoftwareSection] {
    override def getAll(dataStream: InputStream): Seq[DataSoftwareSection] = Seq(DataSoftwareSection("data", SoftwareAddress(0), 0))
  }
}

My business logic, located in the project core needs to call the method sectionsOfType of an instance of GetSectionsOfType, which will be gently injected using cake pattern.

Issue to resolve the implicit type class

As the project core does not have (and should not have!) a dependency on the infra project, calling the method sectionsOfType[CodeSoftwareSection].getAll(openStream) makes the compiler angry, because it can't find any implicit value for the implicit parameter, which is normal!

Now, I can't just import all the implementations of GetSectionsOfType because it would expose the implementation in my project core. In order to resolve this situation, I read the article WHERE DOES SCALA LOOK FOR IMPLICITS? from the Scala docs, hoping to find another way to resolve my implicit, without creeping my project core with a dependency on my project infra.

Unfortunately, I could not find any way to resolve my implicit, as the project core can't link the project infra and the implicit parameters must be resolved at compile time.

Any proposal or alternative approach to keep the code clean ?

EDIT 3: Refactored the code with cake pattern.
EDIT 4: Add an example of call to the component in the project core below.

trait SoftwareComp {
  this: GetSectionsOfTypeComp =>

  class Software(val file: File) {

    def codeSections[SectionType <: SoftwareSection]: Seq[SectionType] = {
      sectionsOfType[SectionType].getAll(openStream)
    }

    def openStream: InputStream = new FileInputStream(file)
  }

}
1

1 Answers

0
votes

Some observations on your current code:

In your GetSectionsOfTypeComp you can write

trait GetSectionsOfTypeComp {
  def sectionsOfType[SectionType <: SoftwareSection](implicit evidence: GetSectionsOfType[SectionType]): GetSectionsOfType[SectionType] = evidence
}

if all you do is return the evidende in every (or at least many) implementations.

Also, you do not necessarily have to create an implicit val for you implicit objects.

This is definitely not a must but you can avoid creating subtypes for GetSectionsOfTypeComp and at the same time separate the type class code. You would end up with something of this kind:

import java.io.InputStream

// type class code
trait GetSectionsOfType[SectionType <: SoftwareSection] {
  def getAll(dataStream: InputStream): Seq[SectionType]
}

object GetSectionsOfType {
  implicit object GetSectionOfCode extends GetSectionsOfType[CodeSoftwareSection] {
    override def getAll(dataStream: InputStream): Seq[CodeSoftwareSection] = Seq(CodeSoftwareSection("code", SoftwareAddress(0), 0))
  }

  implicit object GetSectionOfData extends GetSectionsOfType[DataSoftwareSection] {
    override def getAll(dataStream: InputStream): Seq[DataSoftwareSection] = Seq(DataSoftwareSection("data", SoftwareAddress(0), 0))
  }
}

// component 
trait GetSectionsOfTypeComp {
  def sectionsOfType[SectionType <: SoftwareSection](implicit evidence: GetSectionsOfType[SectionType]): GetSectionsOfType[SectionType] = evidence
}

As for the actual question, I do not know all your code but you define the core project as a place for trait definitions. Is it a good idea for the core project to know about implementations? Should you not consider to place the calls elsewhere (e.g. a new module)? To decide this more knowledge about the call site would be required.

Hope this helps.