我想在Haskell中创建自己的monad,让Haskell就像任何其他内置的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.return
和Prelude.>>=
区分开来。如果我希望Haskell像任何其他类型的monad一样处理M
,并正确地重载Prelude
中的monad运算符,我该怎么做?
答案 0 :(得分:11)
要使新的monad与所有现有的Haskell机制一起使用 - 例如do
符号 - 您需要做的就是将类型声明为Monad
类型类的实例。然后,Prelude
函数>>=
,return
等将与您的新类型一起使用,就像处理所有其他Monad
类型一样。
但是,存在一个限制,需要对示例进行一些更改。类型同义词(用type
声明)不能成为类实例。 (您的M a
与 Int -> (a, Int)
完全相同。)您需要使用data
或newtype
。 (这两者之间的区别在这里不相关。)
这两个关键字都创造了一种真正的新类型;特别是,他们创建了一个新的数据构造函数。您应该在任何基本的Haskell文本中阅读此内容。简而言之,newtype X a = Y (...)
创建了一个新的类型 X a
;您可以使用构造函数Y
创建该类型的值(它可以且通常与类型构造函数X
具有相同的名称);您可以通过Y
上的模式匹配消费值。如果您选择不导出数据构造函数Y
,则只有模块中的函数才能直接操作这些值。
(还有GHC扩展程序TypeSynonymInstances
,但由于单独的问题,它无法帮助您:类型同义词无法部分应用;对于任何type X a = {- ... -}
,您只能写X a
或X Int
或其他内容,而不只是X
。您无法写instance Monad M
,因为M
已部分应用。)
之后,您需要做的就是将return
和>>=
的定义移到instance Monad
声明中:
newtype M a = M (State -> (a, State))
instance Monad M where
return a = M $ \x -> (a, x)
m >>= k = {- ... -}
请注意,(>>=)
的实现稍微冗长,因为您需要使用其数据构造函数newtype
来展开和重新包装M
。查看the implementation of StateT
in transformers
,它使用记录访问器使其更容易。 (您可以手动编写与runM :: M -> State -> (a, State)
和许多其他软件包使用的记录语法等效的函数transformers
。)
答案 1 :(得分:0)
这是一个实现:
-- 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"