“Is almost a” relationships in Scala

I'm trying to design a class hierarchy with a bunch of similar classes that don't quite share a "is a" relationships. Let's call these Model classes. These classes are meant to be paired with a collection similar algorithms which make use the Model classes but do not identically have the same requirements. Let's call these Strategy classes. The trick is that the Strategy classes require many of the same things from the Model classes, but not all Model classes may be able to implement those required methods. I'd like to not have empty "stub" methods that will just throw UnsupportedOperationExceptions and instead have a type-safe mixin-based way of dealing with this -- is there a design pattern that I can apply?

For example,

object Main extends App {
    trait A {
        def f(one: Int): Int
        def g(two: Int): Int
        def h(three: Int): Int
    }

    class A1 extends A {
        override def f(one: Int): Int = {one + 1}
        override def g(two: Int): Int = {two + 2}
        override def h(three: Int): Int = {assert(false); 0}
    }

    class A2 extends A {
        override def f(one: Int): Int = {assert(false); 0}
        override def g(two: Int): Int = {two - 2}
        override def h(three: Int): Int = {three - 3}
    }

    trait B {
        def combine(i: Int): Int
    }

    trait B1 extends B {
        this: A =>
        override def combine(i: Int) = {f(i) + g(i)}
    }

    trait B2 extends B {
        this: A =>
        override def combine(i: Int) = {g(i) + h(i)}
    }

    override def main(args: Array[String]): Unit = {
        val a11 = new A1 with B1
        val a22 = new A2 with B2

        println(a11.combine(3))
        println(a22.combine(3))

        val a12 = new A1 with B2
        println(a12.combine(3))
    }
}

Here A is a Model class and B is the Strategy class. Notice that A1 may not be able to implement h() and that A2 may not be able to implement f(), and depending on the strategy class this may or may not be a problem. I'd like to be able to find out which implementation of A can work with which implementation of B at compile time.

I've used self-types to express a more "has a" than "is a" relationship that would normally go with extension.

Answers


You could also use structural types, the classes don't even need to be related in their hierarchy:

class A1 {
  def f(i: Int) = {i + 1}
  def g(i: Int) = {i + 2}
}

class A2 {
  def g(i: Int) = {i * 2}
  def h(i: Int) = {i * i}
}

type FnG = { def f(i: Int): Int; def g(i: Int): Int}

class B {
  def combine1(a: FnG, i: Int) = a.f(i) + a.g(i)
  def combine2(a: { def g(i: Int): Int; def h(i: Int): Int}, i: Int) = 
      a.g(i) + a.h(i)
}

val a1 = new A1
val a2 = new A2
val b = new B
println(b combine1(a1, 3))
println(b combine2(a2, 3))

trait C {
  def combine(i: Int): Int
}
trait C1 extends C {
  this: FnG =>
  def combine(i: Int) = f(i) + g(i)
}
trait C2 extends C {
  this: { def g(i: Int): Int; def h(i: Int): Int} =>
  def combine(i: Int) = g(i) + h(i)
}

val newA1 = new A1 with C1
val newA2 = new A2 with C2

println(newA1 combine(3))
println(newA2 combine(3))

This way, you only need to specify (for traits) that the base types supports specific methods, and for classses, that the passed in class supports specific methods. You don't need to assume any hierarchy.


Here my solution:

trait F { def f(one: Int): Int }
trait G { def g(two: Int): Int }
trait H { def h(three: Int): Int }

trait A
trait A1 extends A with F with G {
  def f(one: Int): Int = { one + 1 }
  def g(two: Int): Int = { two + 2 }
}
trait A2 extends A with G with H {
  def g(two: Int): Int = { two - 2 }
  def h(three: Int): Int = { three - 3 }
}

trait B {
  def combine(i: Int): Int
}
trait B1 extends B {
  this: A with F with G =>
  def combine(i: Int) = { f(i) + g(i) }
}
trait B2 extends B {
  this: A with G with H =>
  def combine(i: Int) = { g(i) + h(i) }
}

val a11 = new A1 with B1
val a22 = new A2 with B2

println(a11.combine(3))
println(a22.combine(3))

val a12 = new A1 with B2 // won't compile as you wanted

Need Your Help

How to bind PivotItems dynamically to html pages

javascript winjs windows-phone-8.1

So I'm trying to create an app with a WinJS.UI.Pivot control. The documentation is very simple, and the samples I've seen are "for dummies".

Are conditional expressions in C++ always of bool type?

c++ c c++11 language-lawyer c++03

In C conditional-oriented operators evaluate to either 1 or 0 of type int (even if it does have dedicated _Bool type). Referring to C11 N1570 draft:

About UNIX Resources Network

Original, collect and organize Developers related documents, information and materials, contains jQuery, Html, CSS, MySQL, .NET, ASP.NET, SQL, objective-c, iPhone, Ruby on Rails, C, SQL Server, Ruby, Arrays, Regex, ASP.NET MVC, WPF, XML, Ajax, DataBase, and so on.