liveBook · Manning

Callback

As explained in Functional Programming in Scala, Second Edition

A callback is a function supplied to an asynchronous operation so that the operation can report completion or a result later.

A callback is a function handed to an operation now so it can handle a value later. In a non-blocking representation, a Future does not expose a blocking get; it accepts an A => Unit callback, also called a continuation, and invokes it when A has been computed. The callback need not run immediately. section 7.3.4

This shifts the boundary of an asynchronous program. A callback-based byte source returns immediately, while its callback receives either an error or the requested bytes when the I/O subsystem finishes. Calling such APIs directly spreads completion handlers through application code; wrapping them as Par values preserves a compositional interface. section 13.5

A callback adapter follows a small, explicit path:

  • Accept the callback expected by Par.async. section 13.5
  • Start the underlying asynchronous request and pass it a completion handler. section 13.10
  • Have completed and failed forward a Right result or Left error to that callback. section 13.10
  • Suspend the resulting Par action when it must participate in a Free program. section 13.10

Wrapping an asynchronous read

A Source.readBytes call takes a callback of type Either[Throwable, Array[Byte]] => Unit. nonblockingRead passes Par.async a function that calls source.readBytes(numBytes, cb). The source retains cb until bytes or an error are available; the surrounding code receives a Par value instead of manually coordinating completion. section 13.5

def nonblockingRead(source: Source, numBytes: Int):
    Par[Either[Throwable, Array[Byte]]] =
  async: (cb: Either[Throwable, Array[Byte]] => Unit) =>
    source.readBytes(numBytes, cb)
 
def readPar(source: Source, numBytes: Int):
    Free[Par, Either[Throwable, Array[Byte]]] =
  Suspend(nonblockingRead(source, numBytes))

Shows the direct forwarding adapter from a callback-based byte source into Par, then into Free[Par, ...]. section 13.5

Callbacks are imperative at the implementation boundary because A => Unit uses its input to perform an effect rather than return a useful result. That does not make the public Par API impure: the Future representation is opaque, so its local effects are not observable by code using Par. Higher-level operations can then combine asynchronous results; for example, map2 uses an Actor to coordinate whichever callback arrives first and invoke a combined callback once both values exist. section 7.3.4

How Functional Programming in Scala, Second Edition covers it

How deep it goes
Defines callback-based Future encoding and traces adapters through Par, Free, completion handlers, and Actor-based map2.
Examples
Uses byte-source reads, AsynchronousFileChannel completion handlers, and parallel map2 result coordination.
What it assumes
Introduces callbacks through Scala function types while using Par and Free as the surrounding abstractions.
What you can do after
Apply it via Par.async to wrap a callback-based I/O request as a composable Par action.
How it connects
Connects callbacks with continuations, Future, Par, Free, Actor, map2, and non-blocking I/O.

Also explained in

Compare how 19 books explain callback →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Functional Programming in Scala, Second Edition. The section links point to the parts of the book it draws on.