{-# LANGUAGE ScopedTypeVariables, DeriveDataTypeable #-}
{-# LANGUAGE FlexibleContexts #-}

{-|
   Lifted 'IO' like 'Alternative' instance. Due to technical limitation,
   Lifted 'IO' is not an instance of 'Alternative'. ('<||>') is provided
   instead.
-}

module Data.Alternative.IO.Lifted where

import Control.Exception (Exception, SomeException)
import Control.Exception.Lifted (catch, throwIO)
import Control.Monad.Base (MonadBase)
import Control.Monad.IO.Class
import Control.Monad.Trans.Control (MonadBaseControl)
import Data.Typeable
import Prelude hiding (catch)

{-| If the left 'IO' monad of ('<||>') causes an error or 'goNext' is used,
   the right 'IO' monad is executed.
-}
(<||>) :: MonadBaseControl IO m => m a -> m a -> m a
x <||> y = x `catch` (\(_ :: SomeException) -> y)

{-| Go to the next 'IO' monad by throwing 'AltIOLiftedGoNext'.
-}
goNext :: (MonadIO m, MonadBase IO m) => m a
goNext = throwIO AltIOLiftedGoNext

{-| Run any one lifted 'IO' monad.
-}
runAnyOne :: (MonadIO m, MonadBaseControl IO m) => [m a] -> m a
runAnyOne = foldr (<||>) goNext

data AltIOLiftedGoNext = AltIOLiftedGoNext deriving (Show, Typeable)

instance Exception AltIOLiftedGoNext