\( \DeclareMathOperator{\tr}{tr} \newcommand\D{\mathrm{d}} \newcommand\E{\mathrm{e}} \newcommand\I{\mathrm{i}} \newcommand\bigOh{\mathcal{O}} \newcommand{\cat}[1]{\mathbf{#1}} \newcommand\curl{\vec{\nabla}\times} \newcommand{\CC}{\mathbb{C}} \newcommand{\NN}{\mathbb{N}} \newcommand{\QQ}{\mathbb{Q}} \newcommand{\RR}{\mathbb{R}} \newcommand{\ZZ}{\mathbb{Z}} % For +---- metric \newcommand{\BDpos}{} \newcommand{\BDneg}{-} \newcommand{\BDposs}{\phantom{-}} \newcommand{\BDnegg}{-} \newcommand{\BDplus}{+} \newcommand{\BDminus}{-} % For -+++ metric \newcommand{\BDpos}{-} \newcommand{\BDposs}{-} \newcommand{\BDneg}{} \newcommand{\BDnegg}{\phantom{-}} \newcommand{\BDplus}{-} \newcommand{\BDminus}{+} \)
UP | HOME

Input/Output in SML

Table of Contents

1. Introduction/Overview

I'm curious about the implementation details of input/output in Haskell and Standard ML, specifically how they compare to each other.

The Standard ML input/output seems to be specified in the Standard Basis library.

1.1. Observation

There seems to be no adequate way to formally specify input/output. Consequently, this is the only source of impurity which does not appear in the formal definition of Standard ML.

Perhaps I am wildly optimistic and/or ignorant here. For example, there may be some impurity in concurrent programming that I am unaware of…

I also do not believe there is any adequate way to reason about IO. The naive solution is to treat Standard ML's streams like a stack of strings (possibly with metadata like timestamps). But this is inadequate for concurrent systems.

2. Haskell

Haskell's IO Monad works with an abstraction called "handles". They are defined as (stripping back to bare bones):

data Handle
  = FileHandle FilePath !(MVar Handle__)
  | DuplexHandle FilePath !(MVar Handle__) !(MVar Handle__)

data Handle__ = Handle__ {
    haFD :: !FD
    haType :: HandleType
    haIsBin :: Bool
    haIsStream :: Bool
    haBufferMode :: BufferMode
    haBuffer :: !(IORef Buffer)
    haBuffers :: !(IORef BufferList)
    haOtherSide :: (Maybe (MVar Handle__))
}

Then openFile :: FilePath -> IOMode -> IO Handle creates a new IO monad wrapping around any IO computations involving the given file.

Roughly, a Handle is approximately equivalent to a union of a Reader and Writer in Standard ML. Instead of file descriptors, Standard ML uses OS.IO.iodesc for a platform-independent way to describe "files". Then Haskell's IO roughly corresponds to the TextIO module in Standard ML, but in a monadic fashion.

Last Updated: Wed, 23 Sep 2026 08:19:52 -0700