Search code examples
scalanamed-parameters

define default named arguments in terms of other arguments in scala


I have a case class that stores three tied parameters. I'd like to define companion object that may build the class from any two parameters, something that looks like the sample below, that is obviously incorrect:

def test(start : Float = end - duration, duration : Float = end - start, end : Float = start + duration) {
  require( abs(start + duration - end) < epsilon )
  ...
}
val t1 = test(start = 0f, duration = 5f)
val t2 = test(end = 4f, duration = 3f)
val t3 = test(start = 3f, end = 5f)

What tricks I may use to get similar usage syntax?


Solution

  • You can use type-classes:

    // Represents no argument
    object NoArg
    
    // Resolves start, duration, stop
    trait DurationRes[A,B,C] {
      def resolve(s: A, d: B, e: C): (Float, Float, Float)
    }
    
    object DurationRes {
      implicit object startEndRes extends DurationRes[Float, NoArg.type, Float] {
        def resolve(s: Float, d: NoArg.type, e: Float) = (s, e-s, e)
      }
      implicit object startDurRes extends DurationRes[Float, Float, NoArg.type] {
        def resolve(s: Float, d: Float, e: NoArg.type) = (s, d, s+d)
      }
      // etc.
    }
    
    def test[A,B,C](start: A = NoArg, dur: B = NoArg, end: C = NoArg)
                   (implicit res: DurationRes[A,B,C]) {
      val (s,d,e) = res.resolve(start, dur, end)
      // s is start, d duration, e end
    }
    
    test(start = 1f, end = 2f)
    

    This way it is even type-safe and you cannot call something like:

    test(start = 1f)
    

    or even

    test()