Bluefin prevents handles leaking

As a bonus, here’s a Monad that doesn’t let you escape resources out of the do block, similar to how it is done with runST:

{-# LANGUAGE DerivingVia #-}
{-# LANGUAGE BlockArguments #-}
module ScopedCont where

import System.IO qualified
import Control.Monad.Codensity
import UnliftIO (MonadIO, IOMode (WriteMode), liftIO)
import Data.Kind
import Control.Monad.Trans (MonadTrans, lift)

newtype SCod s (m :: k -> Type) a = MkSCod {unSCod :: forall b. (a -> m b) -> m b}
  deriving (Functor, Applicative, Monad) via (Codensity m)
  deriving MonadTrans via Codensity

instance MonadIO m => MonadIO (SCod s m) where 
  liftIO = lift . liftIO


runSCod :: (Applicative m) => (forall s. SCod s m r) -> m r
runSCod k = unSCod k pure

newtype Handle s = MkHandle {unsafeGetHandle :: System.IO.Handle}

withFile :: FilePath -> System.IO.IOMode -> SCod s IO (Handle s)
withFile fp md = MkSCod (\k -> System.IO.withFile fp md (k . MkHandle))

hPutStrLn :: Handle s -> String -> SCod s IO ()
hPutStrLn (MkHandle hdl) = liftIO $ System.IO.hPutStrLn hdl

x :: IO ()
x = runSCod do 
  hdl <- withFile "bla" WriteMode
  liftIO $ hPutStrLn hdl "test"

y :: IO (Handle s)
y = runSCod do 
  withFile "bla" WriteMode
1 Like