How do I use BiConsumer and BiAcceptor with two parameters?

← Generics · Ref: Q711

BiConsumer and BiAcceptor are the void-returning two-parameter function types. They process two values 't' and 'u' without returning a result.

BICONSUMER IS PURE

BiConsumer of (T, U) takes parameters 't' and 'u' and returns nothing. Being pure, it cannot call impure functions:

  validator <- () is BiConsumer of (String, Integer) as pure function
    require t? and u?

BIACCEPTOR IS IMPURE

BiAcceptor of (T, U) has the same signature but is NOT pure:

  logger <- () is BiAcceptor of (String, Integer) as function
    stdout <- Stdout()
    stdout.println(`Name: ${t}, Age: ${u}`)

USE CASES

BiConsumer: Pure validation of two related values.
BiAcceptor: Logging, formatting, or storing pairs of values.

See Q54 for pure vs impure concepts. See Q707 for single-parameter Consumer/Acceptor.

Example

defines module qa.genericsdeep.biconsumerbiacceptor

  defines function

    <?-
      Helper that validates a pair using a pure BiConsumer.
    -?>
    validateWith() as pure
      ->
        name as String
        age as Integer
        validator as BiConsumer of (String, Integer)
      validator(name, age)

    <?-
      Helper that processes a pair using an impure BiAcceptor.
    -?>
    processPair()
      ->
        name as String
        age as Integer
        handler as BiAcceptor of (String, Integer)
      handler(name, age)

  defines program

    BiConsumerBiAcceptorDemo()
      stdout <- Stdout()

      //BiConsumer: pure, validation only
      //Body uses 't' and 'u' — from BiConsumer's signature
      validator <- () is BiConsumer of (String, Integer) as pure function
        require t? and u?

      validateWith("Steve", 42, validator)
      stdout.println("BiConsumer validation passed")

      //BiAcceptor: impure, can log
      logger <- () is BiAcceptor of (String, Integer) as function
        stdout <- Stdout()
        stdout.println(`Name: ${t}, Age: ${u}`)

      processPair("Alice", 30, logger)

      //Direct call
      validator("Bob", 25)
      stdout.println("Direct BiConsumer call passed")

Common mistakes

E05150 — BiConsumer is pure — dynamic functions extending BiConsumer must use 'as pure function', not just 'as function'. The compiler requires the purity of the implementation to match its pure super type. See ek9 -h E05150 for details.

Incorrect:

() is BiConsumer of (String, Integer) as function

Correct:

() is BiConsumer of (String, Integer) as pure function
Other ways to ask this
  • What is the difference between BiConsumer and BiAcceptor?
  • How do I create a BiConsumer of (String, Integer)?
  • When should I use BiAcceptor instead of BiConsumer?

Coming from another language?

Java: java.util.function.BiConsumer<T,U> — no purity distinction. Kotlin: (T, U) -> Unit — no pure/impure variants. Rust: Fn(&T, &U) vs FnMut(&T, &U) — structural. Go: func(T, U) — no purity. C#: Action<T1, T2> — no purity concept. EK9: BiConsumer (pure) vs BiAcceptor (impure) — compile-time enforced purity.

Keywords: callback, pure, void, function-type, side-effect, biconsumer, biacceptor, generic, impure, two-parameter, pair