繁体   English   中英

在Haskell中定义一个新monad?

[英]Define a new monad in Haskell?

我想在Haskell中创建自己的monad,并让Haskell像任何其他内置的monad一样对待它。 例如,下面是用于创建monad的代码,该monad在每次调用时更新一些全局状态变量,以及使用它来计算调用quot函数的次数的求值器:

-- define the monad type
type M a = State -> (a, State)
type State = Int

-- define the return and bind operators for this monad
return a x = (a, x)
(>>=) :: M a -> (a -> M b) -> M b
m >>= k = \x -> let (a,y) = m x in
                let (b,z) = k a y in
                (b,z)

-- define the tick monad, which increments the state by one
tick :: M ()
tick x = ((), x+1)

data Term = Con Int | Div Term Term
-- define the evaluator that computes the number of times 'quot' is called as a side effect
eval :: Term -> M Int
eval (Con a)   = Main.return a
eval (Div t u) = eval t Main.>>= \a -> eval u Main.>>= \b -> (tick Main.>>= \()->Main.return(quot a b))

answer :: Term
answer = (Div (Div (Con 1972)(Con 2))(Con 23))

(result, state) = eval answer 0

main = putStrLn ((show result) ++ ", " ++ (show state))

正如现在实现的那样, return>>=属于Main命名空间,我必须将它们与Prelude.returnPrelude.>>=区分开来Prelude.>>= 如果我希望Haskell像对待任何其他类型的monad一样对待M ,并且正确地使Prelude的monad运算符超载,我该怎么做呢?

为了与所有现有的哈斯克尔新的单子工作machinery-- do记号,例如-所有你需要做的就是声明你的类的实例Monad类型类。 那么Prelude函数>>=return等将与你的新类型一起使用,就像它们对所有其他Monad类型一样。

但是,有一个限制,需要对您的示例进行一些更改。 类型同义词(用type声明)不能成为类实例。 (你的M a Int -> (a, Int) 完全相同 。)你需要使用datanewtype (这两者之间的区别在这里是不相关的。)

这两个关键字都创建了一个真正的新类型; 特别是,他们创建了一个新的数据构造函数 您应该在任何基本的Haskell文本中阅读此内容。 简而言之, newtype X a = Y (...)创建一个新类型 X a ; 你可以使用构造函数Y 创建该类型的值(它可以,并且通常具有与类型构造函数X相同的名称); 你可以通过Y上的模式匹配来消耗值。 如果选择不导出数据构造函数Y ,则只有模块中的函数才能直接操作值。

(有一个GHC扩展TypeSynonymInstances但它不会帮助你,因为一个单独的问题:类型同义词不能部分应用;对于任何type X a = {- ... -}你只能写X aX Int或者诸如此类,永远不只是X你不能编写instance Monad M因为M部分应用。)

之后,您需要做的就是将return>>=定义移动到instance Monad声明instance Monad

newtype M a = M (State -> (a, State))

instance Monad M where
  return a = M $ \x -> (a, x)
  m >>= k = {- ... -}

需要注意的是执行(>>=)稍微详细,因为你需要解开并重新包装newtype使用其数据的构造函数M 查看transformersStateT的实现 ,它使用记录访问器使其更容易。 (您可以手动编写函数runM :: M -> State -> (a, State)等效于transformers和许多其他包使用的记录语法。)

这是一个实现:

    -- Otherwise you can't do the Applicative instance.
    import Control.Applicative


    -- Simple function
    foo :: String -> String
    foo x = do
        x ++ "!!!"

    -- Helper for printing Monads
    print2 :: (Show a) => MyBox a -> IO()
    print2 (MyBox x) = print x


    -- Custom type declaration
    data MyBox a = MyBox a


    -- MyBox functor
    instance Functor MyBox where  
        fmap f (MyBox x) = MyBox (f x)


    -- MyBox Applicative 
    instance Applicative MyBox where  
        pure = MyBox 
        (MyBox f) <*> x = f <$> x


    -- MyBox Monad
    instance Monad MyBox where  
        return x = MyBox x
        MyBox x >>= f  = f x 

    -- (MyBox as a functor) Use a function with a wrapped value
    result1 = foo <$> (MyBox "Brian")

    -- (MyBox as an Applicative) Use a wrapped function with a wrapped value
    result2 = (MyBox foo) <*> (MyBox "Erich")

    -- (MyBox as a Monad)  Use a wrapped value with a lambda (it can be chainable)
    myLambda1 = (\ x -> MyBox (x ++  " aaa"))
    myLambda2 = (\ x -> MyBox (x ++  " bbb"))
    myLambda3 = (\ x -> MyBox (x ++  " ccc"))
    result3 = (MyBox "Rick") 
        >>= myLambda1
        >>= myLambda2
        >>= myLambda3


    -- Another Monad syntax
    result4 = do
        x <- MyBox "A" 
        y <- MyBox "B"  
        z <- MyBox "C"  
        MyBox (x ++ y ++ z)


    main = do
        print2(result1) -- "Brian!!!"
        print2(result2) -- "Erich!!!"
        print2(result3) -- "Rick aaa bbb ccc"
        print2(result4) -- "ABC"

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM