1

I would like to create a framework where users are able to subclass a base class, Node which is able to produce a message (here an integer) based on the current instance state, and the properties of another instance (the parameter n). The users should be able to specialize the method getMessage to produce different messages based on the class of the current instance and the class of the parameter n, as shown in the code below.

The function importantAlgorithm uses messages generated by these nodes to compute the final results.

// Classes defined by the framework //
abstract class Node {
    def getMessage(n: Node) : Int
}

def importantAlgorithm(lstNodes1: List[_ <: Node], lstNodes2: List[_ <: Node]) = {

    val results = lstNodes1.zip(lstNodes2).map({case (n1, n2) =>
        // I would like to get the proper message *BASED ON
        // THE TYPE OF N1 and N2*
        val message = n1.getMessage(n2)
        // Do some work with the message
        //...
        //...
        })
    //...
}

// Classes defined by framework users //
class ItemNode(val p: Int) extends Node {
    override def getMessage(n: UserNode) = {
        // Compute message based on this ItemNode member variables
        // and n (instance of UserNode) member variables
    }
    override def getMessage(n: ItemNode) = {
        // Compute message based on this ItemNode member variables
        // and n (instance of UserNode) member variables
        // The algorithm is different from the algorithm
        // used in the previous method
    }
}

class UserNode extends Node {
    override def getMessage(n: OtherNode) = {
        // Compute message. Same idea as above
    }
}

class OtherNode extends Node { 
    override def getMessage(n: UserNode) = {
        // Compute message. Same idea as above
    }
}

// The user should be able to use the framework this way
importantAlgorithm(List(new UserNode(), new ItemNode(236), new OtherNode(),
   List(new OtherNode(), new ItemNode(542), new UserNode()))

Of course, Scala does not allow to specialize a parameter of a method in a subclass and the above code does not compile. I could use isInstanceOf[] or RTTI but I have a feeling that I am not thinking properly and not designing my framework properly. How could I replace the mechanism described in the code sample above by a simpler and cleaner solution ?

Xion345
  • 1,627
  • 12
  • 26

2 Answers2

1

I think you need is something like

trait Node {
  type AppropriateSender <: Node
  def getMessage(n: AppropriateSender): Int
}

class UserNode extends Node {
  type AppropriateSender = OtherNode
  def getMessage(n: OtherNode) = ???
}

...

However, there are some problems caused by type erasure so that you cannot check the compatibility of your n1 and n2 (maybe type tags?), but at least you can implement your staff in a clean way now. Another issue is how you deal with the fact that some node types have more than 1 appropriate sender type (which might be solved by an implementation of raw union type).

Community
  • 1
  • 1
Kane
  • 1,314
  • 2
  • 9
  • 14
  • Yes, the solution you suggested is the solution of the book _Programming in Scala_ Chapter 18.3, Abstract types. However, my big issue is how to deal with multiple `getMessage` methods taking different `Node` types. – Xion345 Feb 14 '13 at 18:18
  • 1
    @Xion345 See the link I provided at the end of the answer, which is a very intelligent trick to implement an unboxed union type (while `Either[A,B]` provided by scala is boxed). Then you can have something like `type AppropriateSender = OtherNode or UserNode`, such that `OtherNode <:< AppropriateSender <:< Node`, which should be exactly what you want if I understand it correctly. – Kane Feb 14 '13 at 18:22
  • 1
    @Xion345 Oh that specific answer I am pointing to is not the accepted one (but the one with nearly same number of votes!). You can also read this article directly: http://www.chuusai.com/2011/06/09/scala-union-types-curry-howard/ – Kane Feb 14 '13 at 18:25
  • I just read the stackoverflow question. Actually, I think that pattern matching is sufficient in my case. I could have a `createMessage(n1: Node, n2: Node)` which computes the correct message based on the type of n1 and n2. Thank you very much for your help. – Xion345 Feb 14 '13 at 18:33
1

Would this suffice? (It compiles...)

/* Classes defined by the framework */
abstract class Node {
    def getMessage(n: Node): Int
}

def importantAlgorithm(lstNodes1: List[Node], lstNodes2: List[Node]) {
  lstNodes1.zip(lstNodes2).map {
    case (n1, n2) =>
      // I would like to get the proper message *BASED ON
      // THE TYPE OF N1 and N2*
      val message = n1.getMessage(n2)
  }
}

/* Classes defined by framework users */
class   ItemNode(val p: Int)
extends Node
{
  def getMessage(n: Node): Int =
    n match {
    // Compute message based on this ItemNode member variables
    // and n (instance of UserNode) member variables
      case un: UserNode => 0
      case in: ItemNode => 1
      case xn: Node     => -1
  }
}

class   UserNode
extends Node
{
  def getMessage(n: Node): Int =
    n match {
      case on: OtherNode => 23
      case xn: Node     => -1
    }
}

class   OtherNode
extends Node
{
  def getMessage(n: Node): Int =
    n match {
      case xn: Node => 514
    }
}

// The user should be able to use the framework this way
importantAlgorithm(List(new UserNode(),
                        new ItemNode(236),
                        new OtherNode()),
                   List(new OtherNode(),
                        new ItemNode(542),
                        new UserNode()))
Randall Schulz
  • 26,420
  • 4
  • 61
  • 81
  • Yes, the code sample you posted solves my issue but I wanted to see if it was possible to design a "pure" object-oriented solution (i.e. without using pattern matching, isInstance or RTTI). However, I don't think this is feasible since the algorithm to be used to generate messages is based on the class of the first node *and* on the class of the second node. – Xion345 Feb 14 '13 at 22:56
  • 1
    @Xion345: You could look into the double-dispatch pattern. It's a way of simulating multi-methods in a single-dispatch OO language like Java or Scala. I have a vague hunch it might work for this case. – Randall Schulz Feb 15 '13 at 00:11
  • Thank you, *double dispatch* is the mechanism I was looking for (I did not know the keyword). I know have to figure out if it is better to use the associated pattern or to use a pattern-matching based solution. – Xion345 Feb 15 '13 at 08:20