{- |
Module      : Acme.Omitted
Description : A universal definition of omitted content
Copyright   : (c) 2013 Joachim Fasting
License     : BSD3

Maintainer  : joachim.fasting@gmail.com
Stability   : stable
Portability : portable

A universal definition of \"omitted content\" and methods
for observing whether a definition is merely \"omitted\" or
truly \"undefined\".
-}

module Acme.Omitted
  (
    -- * A universal definition of \"omitted content\"
    --
    -- $omitted
    omitted
  , (...)

    -- * Observing the difference between \"omitted\" and \"undefined\"
    --
    -- $observing
  , isOmitted
  , isUndefined
  ) where

import qualified Control.Exception as E

------------------------------------------------------------------------
-- $omitted
--
-- The difference between \"omitted\" and \"undefined\" is that the
-- programmer may choose to omit something but he cannot define the
-- undefinable.
-- The former is contingent on the whims of the programmer, the latter
-- a fundamental truth.
--
-- Operationally, there is no difference between undefined and omitted;
-- attempting to evaluate either is treated as an error.
--
-- Ideally, programmers would only ever use 'undefined' for things that
-- are truly undefined, e.g., division by zero, and use 'omitted' for
-- definitions that have yet to be written or that are currently not needed.

-- | Alternative syntax for 'omitted' that has been carefully
-- optimised for programmer convenience and visual presentation
-- (e.g., for use in printed documents).
--
-- Example usage:
--
--    > definition = (...)
(...) :: a
(...) = omitted

-- | The universal omitted content operator.
--
-- This is sufficient to express _all_ types of omitted content
omitted :: a
omitted = error "omitted"

------------------------------------------------------------------------
-- $observing
--
-- The following definitions allow the user to discriminate undefined
-- omitted values.
-- Some caveats apply, however.
--
-- Though 'isUndefined' arguably could be a pure function (what is by
-- definition undefinable shall always remain undefined), we feel it most
-- appropriate to keep both 'isOmitted' and 'isUndefined' in 'IO', for
-- reasons of symmetry and because the distinction between omitted and
-- undefined is a 'GHC.Prim.RealWorld' concern (in the end, both denote the
-- same value, i.e., bottom).
--
-- Another reason to keep 'isUndefined' in 'IO' is the regrettable state of
-- modern Haskell, which has forced programmers to use 'undefined' for all
-- sorts of purposes where 'omitted' should have been used instead.
-- Thus it is unsound to assume that 'undefined' values will remain so, or
-- indeed make any assumptions about it at all.
--
-- The confounding of \"undefined\" and \"omitted\" also means that,
-- as it stands, 'isUndefined' will return bogus results for some uses of
-- 'undefined'.
-- A possible refinement is to provide an alternative to \"Prelude.undefined\"
-- that could be assumed to only represent values that are \"truly undefined\".
-- For now, 'isUndefined' is provided as a convenience, but users are adviced to
-- not rely on its results.
-- Users are, however, encouraged to file bugs against libraries making unsound
-- use of 'undefined'.

-- | Answer the age-old question \"was this definition omitted?\"
--
-- @
-- isOmitted 0         = return False
-- isOmitted undefined = return False
-- isOmitted omitted   = return True
-- @
isOmitted :: a -> IO Bool
isOmitted = isErrorCall "omitted"

-- | ... or is it really 'undefined'?
--
-- @
-- isUndefined 0         = return False
-- isUndefined omitted   = return False
-- isUndefined undefined = return True
-- @
isUndefined :: a -> IO Bool
isUndefined = isErrorCall "Prelude.undefined"

isErrorCall :: String -> a -> IO Bool
isErrorCall s x = (E.evaluate x >> return False)
                  `E.catch` (\(E.ErrorCall e) -> return $ e == s)