在Haskell中定义一个新monad?

Dav*_*fau 4 monads haskell operator-overloading

我想在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))
Run Code Online (Sandbox Code Playgroud)

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

Chr*_*kle 11

为了让你的新monad与所有现有的Haskell机制一起工作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申报:

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

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

请注意,实现(>>=)略有冗长,因为您需要newtype使用其数据构造函数来解包和重新包装M.查看in 的实现StateTtransformers,它使用记录访问器使其更容易.(您可以手动编写与许多其他软件包使用runM :: M -> State -> (a, State)的记录语法等效的函数transformers.)

  • @DavidPfau`newtype`是不同的.您必须使用`M`构造函数显式地包装和解包事物,因为您正在创建一个不会作为包装类型进行类型检查的新类型. (3认同)