[Haskell-cafe] Practical Haskell question.
Arie Peterson
ariep at xs4all.nl
Mon Jun 25 12:40:05 EDT 2007
As others have explained, you can't analyse your do-constructs, because
functions are opaque -- at the value level.
The canonical option would indeed seem to be to use arrows (or applicative
functors), instead of monads.
------
If you want to stick to monads, there is another possibility: carry around
the necessary checks *at the type level*. Below is a sketch of how you
could do this.
Things to note:
- Uses HList <http://homepages.cwi.nl/~ralf/HList/>.
- Deciding which checks to perform happens statically, so it will check
for any actions that are mentioned, even if they are not actually
performed:
actionX >>= \ b -> if b then actionY else actionZ
will perform checks necessary for actionZ, even if actionX happens to
return True.
- First draft; may contain sharp edges (or outright errors). There are
some possibilities for generalisation: e.g. do it over an arbitrary monad,
instead of IO.
------8<------
module CheckIO where
import Control.Monad.Error
import HList
(
(:*:)
, (.*.)
, HNil
(
HNil
)
, HOccurs
)
data CheckIO labels x
= CheckIO (IO x)
instance Monad (CheckIO l) where
return = CheckIO . return
(CheckIO a) >>= h = CheckIO $ a >>= ((\ (CheckIO x) -> x) . h)
fail = CheckIO . fail
instance Functor (CheckIO l) where
fmap f (CheckIO a) = CheckIO (fmap f a)
withCheck :: (HOccurs label labels) => IO x -> label -> CheckIO labels x
withCheck = flip (const CheckIO)
class Check label where
check :: label -> ErrorT String IO () -- |label| argument is for type
inference only
class Checks c where
performChecks :: c -> ErrorT String IO () -- |c| argument is for type
inference only
instance Checks HNil where
performChecks _ = return ()
instance (Check label,Checks rest) => Checks (label :*: rest) where
performChecks _ = check (undefined :: label) >> performChecks (undefined
:: rest)
runWithChecks :: forall labels x. (Checks labels) => CheckIO labels x ->
labels -> ErrorT String IO x
runWithChecks (CheckIO q) _ = performChecks (undefined :: labels) >> liftIO q
-- End of general CheckIO code; the following example use would actually
go in a different module.
-- Component actions
data Root
= Root
instance Check Root where
check _ = do
liftIO $ putStrLn "Root privileges required. Enter root password:"
pw <- liftIO getLine
if pw == "myRootPassword"
then return ()
else throwError "No root."
actionA :: (HOccurs Root labels) => CheckIO labels ()
actionA = putStrLn "Enter a string:" `withCheck` Root
data Database
= Database
instance Check Database where
check _ = liftIO $ putStrLn "Database is ok."
actionB :: (HOccurs Database labels) => CheckIO labels String
actionB = getLine `withCheck` Database
data Connection
= Connection
instance Check Connection where
check _ = do
liftIO $ putStrLn "Connection up?"
x <- liftIO getLine
if x == "yes"
then return ()
else throwError "No connection."
actionC :: (HOccurs Connection labels) => String -> CheckIO labels ()
actionC x = putStrLn (reverse x) `withCheck` Connection
-- Composed action
main :: ErrorT String IO ()
main = action `runWithChecks` (Connection .*. Database .*. Root .*. HNil)
action :: (HOccurs Root labels,HOccurs Connection labels,HOccurs Database
labels) => CheckIO labels ()
action = do
actionA
x <- actionB
actionC x
------>8------
Kind regards,
Arie
More information about the Haskell-Cafe
mailing list