Search code examples
kotlinboilerplateverbosity

Kotlin - Secondary constructor that differs by one argument


I have a snippet of Kotlin code where the first and the secondary constructor differs minutely, see below

class InstructionPrototype constructor(
      val iname: String,
      val opcode: Int,
      val mnemonicExample: String,
      val numericExample: Int,
      val description: String,
      val format: Format,
      val pattern: Pattern,
      var type: Type? = null,
      var rt: Int? = null,
      var funct: Int? = null,
      var conditions: Array<(n: Int) -> String?>? = null) {
  constructor(
        iname: String,
        opcode: Int,
        mnemonicExample: String,
        numericExample: Int,
        description: String,
        format: Format,
        pattern: Pattern,
        type: Type?,
        rt: Int?,
        funct: Int?,
        condition: (n: Int) -> String?
  ): this(iname, opcode, mnemonicExample, numericExample, description, 
        format, pattern, type, rt, funct, arrayOf(condition)) {

  }

is it possible to reduce the verbosity of this through some language construct? I was thinking about algebraic data types but that did not feel like a good fit - it came across as "hacky".


Solution

  • Variable number of arguments (vararg) seems to fit your use case very well, but only if you can abandon null as the default value for conditions since vararg cannot be nullable (e.g. use emptyArray()):

    class InstructionPrototype constructor(
          val iname: String,
          val opcode: Int,
          val mnemonicExample: String,
          val numericExample: Int,
          val description: String,
          val format: Format,
          val pattern: Pattern,
          var type: Type? = null,
          var rt: Int? = null,
          var funct: Int? = null,
          vararg var conditions: (n: Int) -> String? = emptyArray())
    

    At use site, you can pass single (n: Int) -> String?, and it will be packed into an array, and, in addition to passing several functions separated by comma, you can use the spread operator to pass an array:

    f(vararg a: String) { }
    
    f("a")
    f("a", "b", "c")
    
    val array = arrayOf("a", "b", "c")
    f(*array) // any array of the correct type can be passed as vararg
    

    Also, several parameters before conditions also have default values, and there's no other way to skip them and pass conditions than use named arguments and the spread operator:

    fun f(x: Int = 5, vararg s: String) { }
    
    f(5, "a", "b", "c") // correct
    f(s = "a") // correct
    f(s = "a", "b", "c") // error
    f(s = *arrayOf("a", "b", "c") // correct