I have several functions that all take the same 4 arguments. How do I clean this up in EK9?

← Classes and OOP · Ref: Q1064

Extract a record. Group the repeated parameters into a record type and pass that instead.

BEFORE (repeated arguments):

  calculateTotal() as pure
    -> price as Float, quantity as Integer, discount as Float, taxRate as Float
    <- rtn as Float: ...
  formatInvoice() as pure
    -> price as Float, quantity as Integer, discount as Float, taxRate as Float
    <- rtn as String: ...
  isValidOrder() as pure
    -> price as Float, quantity as Integer, discount as Float, taxRate as Float
    <- rtn as Boolean: ...

AFTER (record parameter):

  defines record
    OrderLine
      price as Float: 0.0
      quantity as Integer: 0
      discount as Float: 0.0
      taxRate as Float: 0.0
      default operator
  calculateTotal() as pure
    -> order as OrderLine
    <- rtn as Float: ...
  formatInvoice() as pure
    -> order as OrderLine
    <- rtn as String: ...

Benefits:
- One place to add a new field (not 4 function signatures)
- Record fields are public so functions access them directly
- Records get default operators (==, $, #?, etc.) for free
- The record is a reusable type across your module

See Q97 for record basics. See Q1060 for record fields being public. See Q116 for default operator.

Example

defines module qa.classesandoop.extractrecord

  defines record

    OrderLine
      price as Float: 0.0
      quantity as Integer: 0
      discount as Float: 0.0
      taxRate as Float: 0.0

      OrderLine()
        ->
          price as Float
          quantity as Integer
          discount as Float
          taxRate as Float
        this.price :=: price
        this.quantity :=: quantity
        this.discount :=: discount
        this.taxRate :=: taxRate

      default operator

  defines function

    calculateTotal() as pure
      -> order as OrderLine
      <- rtn as Float?

      subtotal <- order.price * order.quantity
      discounted <- subtotal - (subtotal * order.discount)
      rtn: discounted + (discounted * order.taxRate)

    formatInvoice() as pure
      -> order as OrderLine
      <- rtn as String: `${order.quantity} x ${order.price} = ${calculateTotal(order)}`

    isValidOrder() as pure
      -> order as OrderLine
      <- rtn as Boolean: order.price? and order.quantity? and order.quantity > 0

  defines program

    ExtractRecordDemo()
      stdout <- Stdout()

      order <- OrderLine(price: 29.99, quantity: 3, discount: 0.10, taxRate: 0.20)

      if isValidOrder(order)
        stdout.println(formatInvoice(order))
        stdout.println(`Total: ${calculateTotal(order)}`)

Common mistakes

E11031 — Single-letter parameter names hide their purpose. Extract a record with descriptive field names and pass that instead.

Incorrect:

    calculateTotal() as pure
      -> p as Float, q as Integer, d as Float, t as Float
      <- rtn as Float: p * q

Correct:

    calculateTotal() as pure
      -> order as OrderLine
      <- rtn as Float?
Other ways to ask this
  • How do I reduce repeated parameters across multiple functions?
  • Should I extract a record when many functions share the same arguments?
  • What is the idiomatic way to handle repeated function parameters in EK9?

Coming from another language?

Java: extract a parameter object or record. Python: use a dataclass. EK9: extract a record with default operator.

Keywords: idiomatic, repeated, refactor, parameters, record, extract, arguments