3 ms·
This kind of thing is usually done with typeclasses. You have the “real” monad, but you only use it via the restricted interface allowed by the typeclass constr
by evincarofautumn 14y ago
This kind of thing is usually done with typeclasses. You have the “real” monad, but you only use it via the restricted interface allowed by the typeclass constraints. Off the top of my head:
class (MonadIO m) => MonadTT m where
ttGetLine :: m String
ttGetLine = liftIO getLine
ttPutStrLn :: String -> m ()
ttPutStrLn = liftIO . putStrLn
class (MonadIO m) => MonadFS m where
fsReadFile :: FilePath -> m String
fsReadFile = liftIO . readFile
cat :: (MonadTT m, MonadFS m) => FilePath -> m ()
In reality I don’t think I would structure it quite this way, but you get the general idea. Code to interfaces, not to implementations.
- evincarofautumn 14y agoOkay, now that I have a moment, here’s how to do it with proper encapsulation. The previous example let you perform arbitrary IO with “liftIO”, which is exactly not what you want. class MonadTT m where ttGetLine :: m String ttPutStrLn :: String -> m () instance MonadTT IO where ttGetLine = getLine ttPutStrLn = putStrLn class MonadFS m where fsReadFile :: FilePath -> m String instance MonadFS IO where fsReadFile = readFile cat :: (MonadTT m, MonadFS m) => FilePath -> m ()
- ibotty 14y agoyou are of course right, that that would work. i was wondering more whether it is possible to structure the io monad (using algebras as in data types a la carte) or as you did here while _preserving backwards compatability_. right now io is very broad and can be pretty much everything. it should be more fine-grained. (i still have to look into safe haskell and it's rio, but my superficial look says, it's something different.)