3
\$\begingroup\$

I've written this scala code and I cannot work out how/if it is possible to remove the asInstance in the definition of class Problem.

The basic idea is that there are two hierarchies

Problem
SpecificProblem
ConcreteSpecificProblem

and

SolutionFinder
SpecificSolutionFinder
ConcreteSpecificSolutionFinder

These hierarchies will branched in the future, but at the moment there is only one concrete class and its direct parents.

SolutionFinder classes operate on Problem classes, and on construction of a Problem we provide a SolutionFinder instance that we will operate on it. The type declaration of the Problem class/subclasses specifies which SolutionFinder subclass be used on it, and the SolutionFinder type declaration specifies which Problem classes it be applied to.

The aim is to be able to just call solve on any kind of Problem and have it use the appropriate methods.

As it stands, the code works fine for all the examples I have tried.

I think that it is correct to say in the type declaration of Problem, it is implicit (in the non-scala-keyword sense) that S = S[S,T] = Problem[S,T]. The compiler does not recognise this, perhaps because I'm wrong about this and perhaps because this level of recursive type definition is beyond it's capabilities. To get around this I have told it to convert this to an S using asInstance, which is a faux-pas which I would like to avoid.

So, is there a better way of doing it. Should I be prepared for unforeseen consequences?

object test {

  abstract class SolutionFinder[T <: Problem[S, T], S <: SolutionFinder[T, S]] {
    def findSolution(problem: T): Unit
  }

  abstract class SpecificSolutionFinder[T <: SpecificProblem[S, T], S <: SpecificSolutionFinder[T, S]]
    extends SolutionFinder[T, S] {
    def findSolution(specificProblem: T): Unit = findSpecificSolution(specificProblem)
    def findSpecificSolution(specifProblem:T): Unit
  }

  class ConcreteSpecificSolutionFinder
    extends SpecificSolutionFinder[ConcreteSpecificProblem,ConcreteSpecificSolutionFinder] {
    def findSpecificSolution(specificProblem: ConcreteSpecificProblem): Unit = {
      // Actual solution to problem goes here
    }
  }


  abstract class Problem[T <: SolutionFinder[S, T], S <: Problem[T, S]](solutionFinder: T) {
    def solve(): Unit = solutionFinder.findSolution(this.asInstanceOf[S]) // I WANT TO AVOID THIS
  }

  abstract class SpecificProblem[T <: SpecificSolutionFinder[S, T], S <: SpecificProblem[T, S]](specificSolutionFinder: T)
    extends Problem[T, S](specificSolutionFinder) {
    // Problem specific fields and methods
  }

  class ConcreteSpecificProblem(concreteSpecificSolutionFinder: ConcreteSpecificSolutionFinder)
    extends SpecificProblem[ConcreteSpecificSolutionFinder,ConcreteSpecificProblem](concreteSpecificSolutionFinder) {
    // Problem specific fields and methods with values
  }

  def main(args: Array[String]): Unit ={
    val solutionFinder = new ConcreteSpecificSolutionFinder
    val problem = new ConcreteSpecificProblem(solutionFinder)

    // The aim of the codes structure is to avoid writing
    solutionFinder.findSolution(problem) // (which works, but is not appropriate for how this is used)
    // and write
    problem.solve()
    // instead
  }
}
\$\endgroup\$
  • \$\begingroup\$ best to avoid inheritance and instead use parametric polymorphism and/or type classes. \$\endgroup\$ – Erik Allik Nov 5 '14 at 13:42
3
\$\begingroup\$

There is no implicit inference that S = S[S,T] = Problem[S,T]. But you can add an explicit one:

abstract class Problem[T <: SolutionFinder[S, T], S <: Problem[T, S]](solutionFinder: T) {
  this: S =>
    def solve(): Unit = solutionFinder.findSolution(this)
}

A better design might be to disentangle the Problem and Solution. Perhaps the solve could be moved out of Problem, and require the Solution only at the point of use?

trait Problem
trait Solution[P <: Problem] {def solve(p: P): Unit }
class ConcreteProblem extends Problem
class ConcreteSolution extends Solution[ConcreteProblem]
def solve[P <: Problem](p: P, s: Solution[P]) = s.solve(p)

If you need a more sophisticated relation between Problem and Solution, you could create a typeclass for the "relationship", with implicit instances available:

trait KnowsHowToSolve[P <: Problem, S <: Solution] {
  def solve(p: P, s: S): Unit
}
implicit object ConcreteProblemKnowsHowToSolveConcreteSolution extends KnowsHowToSolve[ConcreteProblem, ConcreteSolution] {
  def solve(p: ConcreteProblem, s: ConcreteSolution) = s.findSpecificSolution(p)
}
def solve[P <: Problem, S <: Solution](p: P, s: S)(implicit ks: P KnowsHowToSolve S) = ...
\$\endgroup\$
0
\$\begingroup\$

The aim is to be able to just call solve on any kind of Problem and have it use the appropriate methods.

Sounds to me like the Strategy pattern. I would not try to approach it in such an object-oriented way, though, if I was writing in Scala.

It's a little hard to give advice, though, because your use case isn't clear - what does the consuming/calling code look like and what's your motivation?

\$\endgroup\$
  • \$\begingroup\$ Yes, it involves the strategy pattern in part. The strategy pattern would be essentially providing various SolutionFinders, which is fine on its own. But there is also a multiplicity of problem types which need to be "matched" with the solutions at the type level (as in, specialist Problems have specific fields and methods that need to be accessible by specific SolutionFinders) \$\endgroup\$ – Lucas Oct 19 '14 at 15:34

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.