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

#!ek9
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

E11062 — A call with 4 or more positional arguments hides what each value means. Group the values in a record and pass them by name (or pass the record itself) - the names then document the call.

Incorrect:

order <- OrderLine(29.99, 3, 0.10, 0.20)

Correct:

order <- OrderLine(price: 29.99, quantity: 3, discount: 0.10, taxRate: 0.20)
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: arguments, extract, idiomatic, parameters, record, refactor, repeated