score:1

Accepted answer

agree with @mik378. but if you are in the process of migrating from the 2 args version to the 3 args version, you can:

trait base {
  // mark this as deprecated
  // no default implementation here because otherwise, a & b would need to 
  // be modified to add the 'override' keyword
  @deprecated
  def compute(arg1:type1, arg2:type2): returntype

  // provide a default implementation for old implementations e.g. a / b
  def compute(arg1:type1, arg2:type2, arg3:type3): returntype =
    compute(arg1, arg2)
}

// convenience base class for new implementations e.g. c
abstract class newbase extends base {
  override def compute(arg1: type1, arg2: type2): returntype =
    throw new unsupportedoperationexception
}

class a  extends base {
  def compute(arg1:type1, arg2:type2): returntype = {
    //detailed implementations
  }
}

class b  extends base {
  def compute(arg1:type1, arg2:type2): returntype = {
    //detailed implementations
  }
}

// all new implementations extend 'newbase' instead of 'base'
class c  extends newbase {
  override def compute(arg1:type1, arg2:type2, arg3:type3): returntype = {
    //detailed implementations
  }
}

and now, you can just use the 3-args version for old & new objs,

val name = objs.map(_.compute(arg1, arg2, arg3))

score:1

you'll either need to define compute(arg1:type1, arg2:type2, arg3:type3) in a and b and defined compute(arg1:type1, arg2:type2) in c or you can provide a default, no-op implementation in your trait

trait base {
    def compute(arg1:type1, arg2:type2) {}
    def compute(arg1:type1, arg2:type2, arg3:type3) {}
}

i'd also recommend defining the return type explicitly in base

edit

a full (simplified) working example using case classes:

trait base {
  def compute(arg1: int, arg2: int): int = 0
  def compute(arg1: int, arg2: int, arg3: int): int = 0
}

case class a() extends base {
  override def compute(arg1: int, arg2: int): int = arg1 + arg2
}

case class b() extends base {
  override def compute(arg1: int, arg2: int): int = arg1 - arg2
}

case class c() extends base {
  override def compute(arg1: int, arg2: int, arg3: int): int = arg1 + arg2 - arg3
}

case class d(arg1: int, arg2: int, arg3: int, objs: seq[base]) {
  val computed = objs map (_ match {
    case x: c    => x.compute(arg1, arg2, arg3)
    case x: base => x.compute(arg1, arg2)
  })
}

score:1

have you heard about interface segregation principle?

the interface-segregation principle (isp) states that no client should be forced to depend on methods it does not use.1 isp splits interfaces that are very large into smaller and more specific ones so that clients will only have to know about the methods that are of interest to them. such shrunken interfaces are also called role interfaces.

source: wikipedia

traits are in some ways similar to those named "interfaces".

basically, you have to split base trait.
traits represent modules in scala and it's a good practice to keep them small so that we increase their ability to be combined and get larger abstractions.

you would end up with two traits: (i merely altered the naming to be clearer)

trait computation {
  def compute(arg1:int, arg2:int): unit
}

trait specificcomputation {
  def compute(arg1:int, arg2:int, arg3:int)
}

class a extends computation {
  def compute(arg1:int, arg2:int) = {
    //detailed implementations
  }
}

class b  extends computation {
  def compute(arg1:int, arg2:int) = {
    //detailed implementations
  }
}

class c extends specificcomputation {
  def compute(arg1:int, arg2:int, arg3:int) = {
    //detailed implementations
  }
}

if you want a class d that should know about those two compute method variants, you write:

class d extends specificcomputation with computation {

      def compute(arg1:int, arg2:int) = {
        //detailed implementations
      }

      def compute(arg1:int, arg2:int, arg3:int) = {
        //detailed implementations
      }
}

Related Query

More Query from same tag