如何使用自定义 App 类型代替 IO?

How to use custom App type in place of IO?

我想在我的程序中使用自定义应用程序类型代替 IO,并将其与 async 库中的 race_ 等函数一起使用。

具体来说,我热衷于将 App 类型的两个计算传递给 race_。由于 race_ 只接受 IO 类型的值,我用 return.

包装了这些计算

虽然进行了类型检查,但我可以看到实际上没有执行任何计算。

这是一个说明问题的最小示例¹:

{-# LANGUAGE MultiParamTypeClasses      #-}
{-# LANGUAGE DerivingStrategies         #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE ScopedTypeVariables        #-}

module RaceTest where

import           Control.Monad.Reader           ( MonadReader
                                                , ReaderT(..)
                                                , runReaderT
                                                )
import           Control.Monad.IO.Class         ( MonadIO
                                                , liftIO
                                                )
import           Control.Concurrent.Async       ( race_ )

data Env = Env { val :: !Int }

newtype App a = App
    { unApp :: ReaderT Env IO a
    } deriving (Functor, Applicative, Monad, MonadIO, MonadReader Env)

runApp :: Env -> App a -> IO a
runApp env app = runReaderT (unApp app) env

main = runApp (Env 24) simpleApp
 where
   simpleApp :: App () = do
     liftIO $ putStrLn "About to spawn threads"
     liftIO $ race_ (return firstAsync) (return secondAsync)
   firstAsync  :: App () = liftIO $ putStrLn "First async"
   secondAsync :: App () = liftIO $ putStrLn "Second async"

如何使用 race_ 运行 这些 App 类型的计算?


¹ 虽然在此示例中删除 App 类型很简单,但在我正在构建的应用程序中,我有 AppEnv 类型允许使用类似于此 setupco-log 进行日志记录。那是我不想失去的东西。

您的计算没有被执行,因为您实际上并没有将它们传递给 race_。相反,您传递了两个 IO 计算,结果是 return 您的 App 计算。但不执行它们。

为了让它们在 IO 中执行,请使用您已有的函数 runApp。由于您需要将环境传递给它,并且我假设您希望使用与 simpleApp 本身相同的环境,因此您可以使用 askMonadReader 上下文:

simpleApp :: App () = do
     liftIO $ putStrLn "About to spawn threads"
     env <- ask
     liftIO $ race_ (runApp env firstAsync) (runApp env secondAsync)