Non-blocking I/O starts an operation and returns immediately instead of occupying the current thread until external input or output completes. A callback receives the result or error when the I/O subsystem is ready. section 13.5
A compositional interface hides the underlying callback-based library. An asynchronous action can be represented as Par[A], where a callback is invoked with the eventual A; Free[Par, A] then sequences such actions without exposing library-specific details. section 7.3.4 section 13.5
The design separates several concerns:
- Represent completion with a callback rather than retrieving a result through a blocking Future.get. section 7.3.4
- Adapt a callback-oriented source into Par with async, preserving success and failure in Either. section 13.5
- Compose multiple reads through Free[Par, A], so programs can sequence abstract I/O actions before execution. section 13.5
- Keep blocking and non-blocking interpreters interchangeable by changing the target monad, while recognizing that ordinary readLine and println remain blocking. section 13.5
Reading bytes through a callback
A Source exposes readBytes(numBytes, callback). nonblockingRead wraps that operation as Par[Either[Throwable, Array[Byte]]], and readPar lifts it into Free[Par, ...]. A larger program can then sequence chunk1 <- readPar(src, 1024) and chunk2 <- readPar(src, 1024) without directly managing the callback protocol. section 13.5
trait Source:
def readBytes(
numBytes: Int,
callback: Either[Throwable, Array[Byte]] => Unit): Unit
Inspect the callback-based Source interface that defines non-blocking byte acquisition. 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))
Inspect the adapter that turns Source.readBytes into a composable Par action. section 13.5




