{-# LANGUAGE ScopedTypeVariables, RecursiveDo, CPP #-}
{-# OPTIONS_GHC -Wall #-}
----------------------------------------------------------------------
-- |
-- Module      :  Data.Unamb
-- Copyright   :  (c) Conal Elliott 2008
-- License     :  BSD3
-- 
-- Maintainer  :  conal@conal.net
-- Stability   :  experimental
-- 
-- Unambiguous choice
----------------------------------------------------------------------

#include "Typeable.h"

module Data.Unamb
  (
    bottom, unamb, assuming, asAgree, hang
  , amb, race
  ) where

import Prelude hiding (catch)
-- For hang
-- import Control.Monad (forever)
import System.IO.Unsafe

-- import Data.Dynamic

import Control.Concurrent
import Control.Exception
  (evaluate, BlockedOnDeadMVar(..), catch, throw)


-- | Unambiguous choice operator.  Equivalent to the ambiguous choice
-- operator, but with arguments restricted to be equal where not bottom,
-- so that the choice doesn't matter.  See also 'amb'.
unamb :: a -> a -> a
a `unamb` b = unsafePerformIO (a `amb` b)


-- | Ambiguous choice operator.  Yield either value.  Evaluates in
-- separate threads and picks whichever finishes first.  See also
-- 'unamb' and 'race'.
amb :: a -> a -> IO a
a `amb` b = evaluate a `race` evaluate b

-- | Race two actions against each other in separate threads, and pick
-- whichever finishes first.  See also 'amb'.  Thanks to Spencer Janssen
-- for this simple version.
race :: IO a -> IO a -> IO a

a `race` b = do v  <- newEmptyMVar
                ta <- forkIO' (a >>= putMVar v)
                tb <- forkIO' (b >>= putMVar v)
                x  <- takeMVar v
                killThread ta
                killThread tb
                return x

-- Use a particular exception as our representation for waiting forever.
-- A thread can bottom-out efficiently by throwing that exception.  If both
-- threads bail out, then the 'takeMVar' would block.  In that case, the
-- run-time system would notice and raise 'BlockedOnDeadMVar'.  I'd then
-- want to convert that exception into the one that wait-forever
-- exception.  As an expedient hack, I use 'BlockedOnDeadMVar' as the
-- wait-forever exception, so that no conversion is needed.  Perhaps
-- revisit this choice, and define our own exception class, for clarity
-- and easier debugging.


-- Fork a thread to execute a given action.  Silence any raised exceptions.
forkIO' :: IO () -> IO ThreadId
forkIO' act = forkIO (act `catch` handler)
 where
   handler :: BlockedOnDeadMVar -> IO ()
   handler = const (return ())

-- I'd like @hang `unamb` hang@ to quickly terminate, throwing an
-- exception.  I'm surprised that it doesn't lead to 'BlockedOnDeadMVar'.
-- Why doesn't it??  Oh -- maybe it does, when compiled.


-- | A 'bottom' value, allowing no information out.  A left- and right-
-- identity for 'unamb'.  At the top level, evaluating 'bottom' results in
-- the message "Exception: thread blocked indefinitely".
bottom :: a
bottom = throw BlockedOnDeadMVar

-- {-# DEPRECATED hang "use bottom instead" #-}

-- | Never yield an answer.  Like 'undefined' or 'error "whatever"', but
-- don't raise an error, and don't consume computational resources.
hang :: a
hang = bottom

-- | Yield a value if a condition is true.  Otherwise wait forever.
assuming :: Bool -> a -> a
assuming c a = if c then a else hang

-- | The value of agreeing values (or hang)
asAgree :: Eq a => a -> a -> a
a `asAgree` b = assuming (a == b) a

----

{-

data WaitForever = WaitForever

INSTANCE_TYPEABLE0(WaitForever,waitForeverTc,"WaitForever")

instance Show WaitForever where
    showsPrec _ WaitForever = showString "waiting for, like, evar"
instance Exception WaitForever

-}

----