hom*_*mam 1 haskell monad-transformers
我有monad变换器对应我的应用程序的独立功能.
在天气模块中:
class Monad m => WeatherT m where
byCity :: String -> m WeatherData
newtype MockWeather m a = MockWeather {
...
} deriving (Functor, Applicative, Monad, MonadTrans)
instance Monad m => WeatherT (MockWeather m) where
...
Run Code Online (Sandbox Code Playgroud)
在Counter模块中:
class Monad m => CounterT m where
increment :: m Int
current :: m Int
newtype MockCounter m a = MockCounter {
...
} deriving (Functor, Applicative, Monad, MonadTrans)
instance Monad m => CounterT (MockCounter m) where
...
Run Code Online (Sandbox Code Playgroud)
它们都可能有多个具有不同实现的实例,例如它们都有我在main中使用的模拟实例:MockCounter和MockWeather.
在Main模块中,我将MyAppmonad 定义为:
newtype MyAppM m a = MyAppM { unMyAppM :: MockCounter (MockWeather m) a }
deriving (Functor, Applicative, Monad, CounterT, WeatherT)
Run Code Online (Sandbox Code Playgroud)
这个定义要求我做(MockCounter (MockWeather m)一个实例WeatherT:
instance Monad m => WeatherT (MockCounter (MockWeather m))
Run Code Online (Sandbox Code Playgroud)
我在主模块中定义了这个实例,因为我不希望Weather和Counter模块相互依赖.
但是在主模块中定义此实例会使其成为孤立实例.
问题:
CounterT,WeatherT并且MyAppM?我想通过组合分离和可模拟的功能来构建我的应用程序.完整代码:
主要模块
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
module Main where
import Counter
import Weather
newtype MyAppM m a = MyAppM { unMyAppM :: MockCounter (MockWeather m) a }
deriving (Functor, Applicative, Monad, CounterT, WeatherT)
instance Monad m => WeatherT (MockCounter (MockWeather m))
runMyAppM :: Int -> MyAppM m a -> m (a, Int)
runMyAppM i = runMockWeather . (`runMockCounter` i) . unMyAppM
myApp :: (Monad m, CounterT m , WeatherT m) => m String
myApp = do
_ <- increment
(WeatherData weather) <- byCity "Amsterdam"
return weather
-- Testing it:
main :: IO ()
main = runMyAppM 12 myApp >>= print
Run Code Online (Sandbox Code Playgroud)
天气模块:
{-# LANGUAGE DefaultSignatures #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
module Weather where
import Control.Monad.Trans.Class
import Control.Monad.Trans.Identity
newtype WeatherData = WeatherData String deriving (Show)
class Monad m => WeatherT m where
byCity :: String -> m WeatherData
default byCity :: (MonadTrans t, WeatherT m', m ~ t m') => String -> m WeatherData
byCity = lift . byCity
newtype MockWeather m a = MockWeather {
unMockWeather :: IdentityT m a
} deriving (Functor, Applicative, Monad, MonadTrans)
runMockWeather :: MockWeather f a -> f a
runMockWeather = runIdentityT . unMockWeather
instance Monad m => WeatherT (MockWeather m) where
byCity city = MockWeather $ return $ WeatherData $ "It is sunny in " ++ city
Run Code Online (Sandbox Code Playgroud)
柜台模块:
{-# LANGUAGE DefaultSignatures #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
module Counter where
import Control.Monad.Identity
import Control.Monad.State
import Control.Monad.Trans.Class
class Monad m => CounterT m where
increment :: m Int
current :: m Int
default increment :: (MonadTrans t, CounterT m', m ~ t m') => m Int
increment = lift increment
default current :: (MonadTrans t, CounterT m', m ~ t m') => m Int
current = lift current
newtype MockCounter m a = MockCounter {
unMockCounter :: StateT Int m a
} deriving (Functor, Applicative, Monad, MonadTrans, MonadState Int)
defaultMockCounter :: MockCounter Identity ()
defaultMockCounter = MockCounter $ put 0
runMockCounter :: MockCounter m a -> Int -> m (a, Int)
runMockCounter = runStateT . unMockCounter
instance Monad m => CounterT (MockCounter m) where
increment = MockCounter $ do
c <- get
let n = c + 1
put n
return n
current = MockCounter get
Run Code Online (Sandbox Code Playgroud)
由于monad变换器这一事实,你需要一个WeatherT m => WeatherT (MockCounter m)只提升WeatherT m实例的实例.(您编写的默认方法的要点是定义此类实例.)MockCounter mMockCounter
为了避免孤立实例,一种方法是分离Weather,Counter每个分成Class和Trans模块.Class不需要相互依赖,而每个Trans模块可能依赖于所有Class模块(反过来也是可能的,事实上mtl它是如何做的,但IMO Trans取决于Class更好:Class定义接口和Trans实现).
这确实是一个(已知的)问题,因为如果你有n变换器和m类,你可能需要n*m提升实例.一种解决方案是为所有变换器定义多态可重叠实例(MonadTrans t, WeatherT m) => WeatherT (t m).重叠的实例经常不受欢迎,但我不确定在这种情况下有什么实际问题.
顺便说一句,遵循命名约定mtl,transformers我们将有MonadWeather和MonadCounter类,WeatherT和CounterT类型(monad变形金刚).