2016-02-12 81 views
1

我试图设计嵌入式语言,其中的操作可以提高某些标志取决于值。我预见了对标量值和向量的操作(例如地图,折叠等)。我的想法是使用Writer Monad来跟踪标志。简单的例子,在实际类型为“内部”,如果任何参数的值为0哈斯克尔写单子的表情

import Control.Monad.Identity 
import Control.Monad.Writer 
import Data.Monoid  

type WInt = Writer Any Int 

bplus :: Int -> Int -> WInt 
bplus a b = 
    do 
     tell (Any (a == 0 || b == 0)) ; 
      return (a+b) 

wbplus :: WInt -> WInt -> WInt 
wbplus wa wb = 
    do 
     a <- wa ; 
     b <- wb ; 
     tell (Any (a == 0 || b == 0)) ; 
      return (a+b) 

ex0 = runWriter (bplus 1 2) 
ex1 = runWriter (bplus 0 2) 

ex2 = runWriter (wbplus (return 1) (return 2)) 
ex3 = runWriter (wbplus (return 0) (return 2)) 

ex4 = runWriter (wbplus (wbplus (return 1) (return 2)) (return 2)) 
ex5 = runWriter (wbplus (wbplus (return 0) (return 2)) (return 2)) 
ex6 = runWriter (wbplus (wbplus (return 1) (return 2)) (return 0)) 

我有点不确定什么是实现这一目标的最佳途径旗升起。有些问题:

  1. 我应该定义所有的操作,比如我做了bplus或像wbplus。看起来,Laters使组合更容易。但要使用foldM二元运算符应具有类型Int -> Int -> WInt

  2. 什么是列表的适当类型:Writer Any [Int][Wint]

任何建议或想法的赞赏。

+0

我认为这是有点不清楚你在问什么。 “我很少不确定什么是最好的实施方式。” - 目前的实施有什么问题?如果你解释你当前实现的哪些部分不令人满意,那么这个问题变得更加清晰。如果解决方案是完全令人满意的,那么我无法想象会有什么问题... – user2407038

+0

我已经澄清了我的一些问题。 – krokodil

+2

我会使用'wbplus'公式 - 它自然适合更好,因为'+'在道德上是一个二元运算符。你不需要使用'foldM' - 'foldr wbplus :: WInt - > [WInt] - > WInt'。你可能会将列表表示为'[WInt]',因为你可以将其转换为'Writer Any [Int]'而不是其他方式('sequence :: [Writer Any Int] - > Writer Any [Int ]') – user2407038

回答

1

您可以使用适当的单子作业产生的wbplus反之亦然bplus

import Control.Monad 

apM2 :: Monad m => (a -> b -> m c) -> m a -> m b -> m c 
apM2 f ma mb = do 
    a <- ma 
    b <- mb 
    f a b 

pureM2 :: Monad m => (m a -> m b -> m c) -> a -> b -> m c 
pureM2 f a b = f (return a) (return b) 

他们是彼此的逆,显而易见他们的作品的类型签名:

ghci> :t pureM2 . apM2 
pureM2 . apM2 :: Monad m => (a -> b -> m c) -> a -> b -> m c 

ghci> :t apM2 . pureM2 
apM2 . pureM2 :: Monad m => (m a -> m b -> m c) -> m a -> m b -> m c 

现在您可以定义wbplus = apM2 bplusbplus = pureM2 wbplus。没有明确的答案哪一个更好,用你的口味和判断力。 TemplateHaskell去与wbplus做法并定义所有的操作与在Q单子值工作。请参阅Language.Haskell.TH.Lib

关于[m a] vs m [a],您只能沿着一个方向行走(通过sequence :: Monad m => [m a] -> m [a])。你会想要朝相反的方向走吗?你关心具有自己旗帜的个人价值吗?或者你是否喜欢用旗帜注释整个计算?

真正的问题是,什么是你的心理模型呢?但是,让我们考虑每个设计选择的一些后果。

  1. 如果您选择代表每个值作为Writer Any a,并有所有的操作使用它,你可以开始newtype

    {-# LANGUAGE GeneralizedNewtypeDeriving #-} 
    
    import Control.Monad.Writer 
    
    newtype Value a = Value (Writer Any a) 
        deriving (Functor, Applicative, Monad) 
    

    现在,您可以定义标准类型的类的实例为您 值:

    instance (Num a, Eq a) => Num (Value a) where 
        va + vb = do 
        a <- va 
        b <- vb 
        (Value . tell . Any) (b == 0 || a == 0) 
        return (a + b) 
        (*) = liftM2 (*) 
        abs = fmap abs 
        signum = fmap signum 
        negate = fmap negate 
        fromInteger = return . fromInteger 
    
        instance Monoid a => Monoid (Value a) where 
        mempty = pure mempty 
        mappend = liftM2 mappend 
    

    对于EDSL来说,这给了一个巨大的优势:编译器提供了简洁和语法支持。您现在可以编写getValue (42 + 0)而不是wbplus (pure 42) (pure 0)

  2. 相反,如果你不想想标记为您的价值观的一部分,而不是将它们看作外部效果,最好还是去了替代的方法。但不要写Writer Any [Int]之类的东西,请使用mtl的相应类:MonadWriter Any m => m [Int]。 这样,如果您后来发现需要使用其他效果,则可以将它们轻松添加到一些(但不是全部)操作中。例如,您可能想通过零提高在分裂的情况下的错误:

    data DivisionByZero = DivisionByZero 
    
        divZ :: (MonadError DivisionByZero m, Fractional a, Eq a) => a -> a -> m a 
        divZ a b 
        | b == 0 = throwError DivisionByZero 
        | otherwise = pure (a/b) 
    
        plusF :: (MonadWriter Any m, Num a, Eq a) => a -> a -> m a 
        plusF a b = do 
        tell (Any (b == 0 || a == 0)) 
        return (a + b) 
    

    现在你可以一个单子中使用plusFdivZ在一起,虽然他们有不同的效果。如果您后来发现自己需要与外部图书馆进行整合,这种灵活性将派上用场。现在

,我没有给它太多心思,但也许你可以结合使用类似newtype Value m a = Value { getValue :: m a }这些方法。祝你好运探索设计空间:)