【问题标题】:Haskell Write Monad for expressionsHaskell 为表达式编写 Monad
【发布时间】:2016-02-12 23:02:14
【问题描述】:

我正在尝试设计嵌入式语言,其中操作可以根据值引发某些标志。我预见到对标量值和向量(例如映射、折叠等)的操作。我的想法是使用 Writer Monad 来跟踪标志。简化示例,其中实际类型为“Int”,如果任何参数为 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. 我应该像为bpluswbplus 那样定义所有操作吗?看起来,Latters 让作曲变得更容易了。但是要使用foldM 二元运算符,应该具有Int -&gt; Int -&gt; WInt 类型。

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

感谢任何建议或想法。

【问题讨论】:

  • 我认为你在问什么有点不清楚。 “我不太确定实现这一点的最佳方式是什么。” - 当前的实施有什么问题?如果您解释当前实现的哪些部分不令人满意,那么问题就会变得更加清晰。如果解决方案完全令人满意,那么我无法想象甚至会有一个问题......
  • 我已经澄清了我的一些问题。
  • 我会使用wbplus 公式——它自然更适合,因为+ 在道德上是一个二元运算符。你不需要使用foldM - foldr wbplus :: WInt -&gt; [WInt] -&gt; WInt。您可能会将列表“表示”为[WInt],因为您可以将其转换为Writer Any [Int],但不能反过来(sequence :: [Writer Any Int] -&gt; Writer Any [Int]

标签: haskell monads writer-monad


【解决方案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 monad 中的值。见Language.Haskell.TH.Lib

关于[m a]m [a],你只能朝一个方向前进(通过sequence :: Monad m =&gt; [m a] -&gt; 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 =&gt; 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)
    

    现在您可以在一个 monad 中同时使用 plusFdivZ,尽管它们有不同的效果。如果您以后发现自己需要与一些外部库集成,这种灵活性将派上用场。

现在,我并没有考虑太多,但也许您可以使用 newtype Value m a = Value { getValue :: m a } 之类的东西来组合这些方法。祝你探索设计空间好运:)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-10-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-04-08
    • 1970-01-01
    • 1970-01-01
    • 2012-10-31
    相关资源
    最近更新 更多