首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >使用Monad Transformers避免孤儿实例

使用Monad Transformers避免孤儿实例
EN

Stack Overflow用户
提问于 2018-03-07 10:36:23
回答 1查看 191关注 0票数 1

我有与我的应用程序的独立功能相对应的单台变压器。

气象模块

代码语言:javascript
复制
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
  ...

计数器模块

代码语言:javascript
复制
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
  ...

它们可能都有具有不同实现的多个实例,例如,它们都有我在这里使用的一个模拟实例:MockCounterMockWeather

主模块中,我将MyApp monad定义为:

代码语言:javascript
复制
newtype MyAppM m a = MyAppM { unMyAppM :: MockCounter (MockWeather m) a }
  deriving (Functor, Applicative, Monad, CounterT, WeatherT)

这个定义要求我使(MockCounter (MockWeather m)成为WeatherT的一个实例。

代码语言:javascript
复制
instance Monad m => WeatherT (MockCounter (MockWeather m))

我在主模块中定义了这个实例,因为我不希望天气模块和计数器模块相互依赖。

但是在主模块中定义这个实例使它成为一个Orphan实例。

问题:

  • 我和CounterTWeatherTMyAppM走在正确的轨道上了吗?我想通过组合解耦和可模拟的功能来构建我的应用程序。
  • 我如何避免孤儿的例子?

全码

主模块

代码语言:javascript
复制
{-# 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

天气模块

代码语言:javascript
复制
{-# 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

计数器模块

代码语言:javascript
复制
{-# 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
EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2018-03-07 11:46:12

您需要一个实例WeatherT m => WeatherT (MockCounter m),该实例通过MockCounter m来提升WeatherT m实例,这要归功于MockCounter是一个单台转换器。(您编写的默认方法的要点是定义此类实例。)

为了避免孤立的实例,一种方法是将WeatherCounter分别划分为ClassTrans模块。Class不需要相互依赖,而每个Trans模块可能依赖于所有的Class模块(相反的方式也是可能的,实际上也是mtl如何实现的,但是IMO Trans依赖于Class更好:Class定义接口,Trans定义实现)。

这确实是一个(已知的)问题,因为如果您有n转换器和m类,则可能需要n*m提升实例。一种解决方案是为所有变压器(MonadTrans t, WeatherT m) => WeatherT (t m)定义一个多态重叠实例。重叠的实例经常被拒绝,但我不确定在这种情况下实际存在什么问题。

顺便说一句,按照mtltransformers的命名约定,我们将有MonadWeatherMonadCounter类,以及WeatherTCounterT类型(monad )。

票数 5
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/49149787

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档