避免使用Monad变形金刚的孤立实例 [英] Avoiding Orphan Instances with Monad Transformers
问题描述
我有我的应用独立功能对应的monad变形金刚。
$ b 在天气模块中:
class Monad m => WeatherT m其中
byCity :: String - > m天气数据
newtype MockWeather ma = MockWeather {
...
}派生(Functor,Applicative,Monad,MonadTrans)
实例Monad m => WeatherT(MockWeather m)其中
...
在计数器模块 strong>:
class Monad m => CounterT m其中
increment :: m Int
current :: m Int
newtype MockCounter ma = MockCounter {
...
}派生(Functor ,应用,Monad,MonadTrans)
实例Monad m => CounterT(MockCounter m)其中
...
它们可能有多个不同的实例例如它们都有一个模拟实例,我在这里使用了它: MockCounter
和 MockWeather
。
在主模块中,我将 MyApp
monad定义为:
newtype MyAppM ma = MyAppM {unMyAppM :: MockCounter(MockWeather m)a}
derived(Functor,Applicative, Monad,CounterT,WeatherT)
这个定义需要我制作(MockCounter MockWeather m)
WeatherT
的一个实例:
实例Monad m => WeatherT(MockCounter(MockWeather m))
我在主模块中定义了这个实例,因为我不想让Weather和Counter模块相互依赖。
但是在主模块中定义这个实例会使它成为Orphan实例。
问题: 完整代码: 主模块 天气模块: b 计数器模块: 为了避免孤立实例,一种方法是将 这确实是一个(已知的)问题,因为如果你有 I have monad transformers corresponding to independent features of my app. In Weather module: In Counter module: They both may have multiple instances with different implementations, for example they both have a mock instance that I use here in my main: In the Main module I define This definition requires me to make I define this instance in the main module, because I don't want Weather and Counter modules to depend on each others. But defining this instance in the main module makes it an Orphan instance. Questions: Main module
Weather module:
Counter module:
You need an instance To avoid orphan instances, one way is to separate This is indeed a (known) problem because if you have By the way, following the naming convention from 这篇关于避免使用Monad变形金刚的孤立实例的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!
CounterT
, WeatherT
和 MyAppM
?我希望通过构建分离和可嘲讽的功能来构建我的应用程序。
$ b { - #LANGUAGE FlexibleInstances# - }
{ - #LANGUAGE GeneralizedNewtypeDiving##}
模块Main其中
导入计数器
导入天气
newtype MyAppM ma = MyAppM {unMyAppM :: MockCounter(MockWeather m)a}
派生(Functor,Applicative,Monad,CounterT,WeatherT)
实例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字符串
myApp =做
_< - 增量
(WeatherData天气)< - byCity阿姆斯特丹
返回天气
- 测试它:
main :: IO()
main = runMyAppM 12 myApp>> = print
{ - #LANGUAGE DefaultSignatures# - }
{ - #LANGUAGE GADTs# - }
{ - #LANGUAGE GeneralizedNewtypeDivingiving# - }
module Weather其中
导入Control.Monad.Trans.Class
导入Control.Monad.Trans.Identity
newtype WeatherData = WeatherData字符串派生(显示)
class Monad m => WeatherT m其中
byCity :: String - > m天气数据
默认byCity ::(MonadTrans t,WeatherT m',m〜t m')=>字符串 - > m WeatherData
byCity = lift。 byCity
newtype MockWeather ma = MockWeather {
unMockWeather :: IdentityT ma
}派生(Functor,Applicative,Monad,MonadTrans)
runMockWeather :: MockWeather fa - > f a
runMockWeather = runIdentityT。 unMockWeather
实例Monad m => WeatherT(MockWeather m)其中
by City City = MockWeather $ return $ WeatherData $++ City
$ b
{ - #LANGUAGE GeneralizedNewtypeDeriving# - }
$ b $计数器
导入Control.Monad.Identity
导入Control.Monad.State
导入Control.Monad.Trans.Class
类Monad m = > CounterT m其中
increment :: m Int
current :: m Int
默认增量::(MonadTrans t,CounterT m',m〜t m')=> m Int
increment =提升增量
默认电流::(MonadTrans t,CounterT m',m〜t m')=> m Int
current = lift current
newtype MockCounter ma = MockCounter {
unMockCounter :: StateT Int ma
}派生函数(Functor,Applicative,Monad ,MonadTrans,MonadState Int)
defaultMockCounter :: MockCounter Identity()
defaultMockCounter = MockCounter $ put 0
runMockCounter :: MockCounter ma - > Int - > m(a,Int)
runMockCounter = runStateT。 unMockCounter
instance Monad m => CounterT(MockCounter m)其中
increment = MockCounter $ do
c< - get
let n = c + 1
put n
return n
current = MockCounter get
WeatherT m =>通过
感谢事实上, MockCounter m
提升 WeatherT m
实例的WeatherT(MockCounter m) MockCounter
是一个monad变换器。 (你写的默认方法的重点是定义这样的实例。)
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
和变形金刚
我们将有 MonadWeather
和 MonadCounter
类, WeatherT
和 CounterT
类型(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
...
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
...
MockCounter
and MockWeather
.MyApp
monad as:newtype MyAppM m a = MyAppM { unMyAppM :: MockCounter (MockWeather m) a }
deriving (Functor, Applicative, Monad, CounterT, WeatherT)
(MockCounter (MockWeather m)
an instance of WeatherT
:instance Monad m => WeatherT (MockCounter (MockWeather m))
CounterT
, WeatherT
and MyAppM
? I want to build my app by composing decoupled and mockable functionalities.{-# 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
{-# 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
{-# 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
WeatherT m => WeatherT (MockCounter m)
which just lifts a WeatherT m
instance through MockCounter m
thanks to the fact that MockCounter
is a monad transformer. (The point of the default methods you wrote is to define such instances.)Weather
and Counter
each into Class
and Trans
modules. Class
don't need to depend on each other, while each Trans
module may depend on all the Class
modules (the other way around is also possible, and is in fact how mtl
does it, but IMO Trans
depending on Class
is better: Class
defines the interface, and Trans
the implementation).n
transformers and m
classes, you potentially need n*m
lifting instances. One solution is to define a polymorphic overlappable instance for all transformers (MonadTrans t, WeatherT m) => WeatherT (t m)
. Overlapping instances are often frowned upon but I'm not sure what actual problems there are in this case.mtl
and transformers
we would have MonadWeather
and MonadCounter
classes, and WeatherT
and CounterT
types (monad Transformers).