Как использовать пользовательский тип приложения вместо ввода-вывода? - PullRequest
2 голосов
/ 01 февраля 2020

Я бы хотел использовать пользовательский тип приложения вместо IO в моей программе и использовать его с такими функциями, как race_ из библиотеки async.

В частности, я заинтересован в передаче двух вычислений типа 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"

Как я могу выполнить эти вычисления типа App, используя race_?


¹ Хотя это в этом примере было бы просто избавиться от типа App, в создаваемом приложении у меня есть типы App и Env, которые позволяют регистрировать с использованием co-log, аналогично этой setup . Это то, что я не хочу терять.

1 Ответ

5 голосов
/ 01 февраля 2020

Ваши вычисления не выполняются, потому что вы на самом деле не передаете их race_. Вместо этого вы передаете два IO вычисления, которые возвращают ваши App вычисления в результате. Но не выполняем их.

Чтобы выполнить их внутри IO, используйте вашу функцию runApp, которая у вас уже есть. Поскольку вам нужно передать ему среду, и я предполагаю, что вы захотите использовать ту же среду, что и сама simpleApp, вы можете использовать ask, чтобы получить ее из контекста MonadReader:

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