在Haskell中定义一个新单子?
我想在Haskell中创建我自己的monad,并让Haskell像对待其他内置monad一样对待它。例如,下面是创建单子的代码,该单子每次调用时都会更新一些全局状态变量,还有一个计算器,使用它计算调用在Haskell中定义一个新单子?,haskell,operator-overloading,monads,Haskell,Operator Overloading,Monads,我想在Haskell中创建我自己的monad,并让Haskell像对待其他内置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
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像对待任何其他类型的单子一样对待M
,并在Prelude
中适当地重载单子操作符,我该如何做呢?让你的新单子与所有现有的Haskell机器一起工作--做符号,例如——您所需要做的就是将您的类型声明为Monad
typeclass的实例。然后Prelude
函数>=
,return
等将与所有其他Monad
类型一样适用于您的新类型
不过,有一个限制,需要对示例进行一些更改。类型同义词(用Type
声明)不能成为类实例。(您的ma
与Int->(a,Int)
完全相同)您需要使用data
或newtype
。(这两者之间的区别与此无关。)
这两个关键词创造了一个真正的新类型;特别是,它们创建了一个新的数据构造函数。你应该在哈斯凯尔的任何一篇基础文章中读到这一点。简而言之,newtypexa=Y(…)
创建一个新类型xa
;您可以使用构造函数Y
(它可以而且通常与类型构造函数X
具有相同的名称)创建该类型的值;您可以通过Y
上的模式匹配来使用值。如果选择不导出数据构造函数Y
,则只有模块中的函数才能直接操作值
(有一个GHC扩展TypeSynonymInstances
,但它在这里对您没有帮助,因为有一个单独的问题:不能部分应用类型同义词;对于任何Typexa={-…-}
你只能写xa
或xint
或诸如此类的东西,而不能只写X
。你不能写实例Monad M
,因为M
是部分应用的。)
之后,您只需将return
和>=
的定义移动到实例Monad
声明中:
newtype M a = M (State -> (a, State))
instance Monad M where
return a = M $ \x -> (a, x)
m >>= k = {- ... -}
请注意,(>>=)
的实现有点冗长,因为您需要使用其数据构造函数M
打开并重新包装newtype
。请看,它使用了一个记录访问器来简化。(您可以手动编写一个函数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"
——否则您无法执行应用程序实例。
导入控制
--简单函数
foo::String->String
foox=do
x++“!!!”
--打印单子的助手
打印2::(显示a)=>MyBox a->IO()
打印2(MyBox x)=打印x
--自定义类型声明
数据MyBox a=MyBox a
--MyBox函子
实例函子MyBox,其中
fmap f(MyBox x)=MyBox(fx)
--MyBox应用程序
实例应用程序MyBox,其中
纯=我的盒子
(MyBox f)x=f x
--MyBox单子
实例Monad MyBox,其中
返回x=MyBox x
MyBox x>>=f=f x
--(MyBox作为函子)使用具有包装值的函数
结果1=foo(MyBox“Brian”)
--(MyBox作为应用程序)使用带包装值的包装函数
结果2=(MyBox foo)(MyBox“Erich”)
--(MyBox作为Monad)使用带lambda的包装值(可以链接)
myLambda1=(\x->MyBox(x++“aaa”))
myLambda2=(\x->MyBox(x++“bbb”))
myLambda3=(\x->MyBox(x++“ccc”))
结果3=(MyBox“Rick”)
>>=myLambda1
>>=myLambda2
>>=myLambda3
--另一种单子语法
结果4=do
x是否同样适用于newtype M a=(State->(a,State))
,或者newtype
在某种程度上与type
不同,因此有必要在右侧添加M
?@DavidPfaunewtype
不同。您必须使用M
构造函数显式地包装和展开内容,因为您正在创建一个新类型,该类型不会作为包装类型进行类型检查。链接部分涉及制作单子。