避免使用Monad变形金刚的孤立实例 [英] Avoiding Orphan Instances with Monad Transformers

查看:134
本文介绍了避免使用Monad变形金刚的孤立实例的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有我的应用独立功能对应的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实例。



问题:


  • 我是否正确地跟踪 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






    天气模块 b

     { - #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 GADTs# - } 
    { - #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变形金刚)。


    I have monad transformers corresponding to independent features of my app.

    In Weather module:

    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
      ...
    

    In Counter module:

    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
      ...
    

    They both may have multiple instances with different implementations, for example they both have a mock instance that I use here in my main: MockCounter and MockWeather.

    In the Main module I define MyApp monad as:

    newtype MyAppM m a = MyAppM { unMyAppM :: MockCounter (MockWeather m) a }
      deriving (Functor, Applicative, Monad, CounterT, WeatherT)
    

    This definition requires me to make (MockCounter (MockWeather m) an instance of WeatherT:

    instance Monad m => WeatherT (MockCounter (MockWeather m))
    

    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:

    • Am I on the right track here with CounterT, WeatherT and MyAppM? I want to build my app by composing decoupled and mockable functionalities.
    • How can I avoid orphan instances?

    Full code:

    Main module

    {-# 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
    


    Weather module:

    {-# 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
    


    Counter module:

    {-# 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
    

    解决方案

    You need an instance 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.)

    To avoid orphan instances, one way is to separate 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).

    This is indeed a (known) problem because if you have 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.

    By the way, following the naming convention from mtl and transformers we would have MonadWeather and MonadCounter classes, and WeatherT and CounterT types (monad Transformers).

    这篇关于避免使用Monad变形金刚的孤立实例的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆