【问题标题】:Define a new monad in Haskell?在 Haskell 中定义一个新的 monad?
【发布时间】:2023-03-18 02:54:01
【问题描述】:

我想在 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.>>= 区分开来。如果我想让 Haskell 像对待任何其他类型的 monad 一样对待 M,并正确地重载 Prelude 中的 monad 运算符,我该怎么做?

【问题讨论】:

  • 类型类在在线书籍learnYouAHaskell中有很好的解释。链接部分涉及制作 monad。

标签: haskell operator-overloading monads


【解决方案1】:

为了让你的新 monad 能够与所有现有的 Haskell 机器一起工作——例如,do 表示法——你需要做的就是将你的类型声明为 Monad 类型类的实例。然后Prelude 函数>>=return 等将与您的新类型一起使用,就像它们与所有其他Monad 类型一样。

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

这两个关键字都创建了一个真正的新类型;特别是,他们创建了一个新的数据构造函数。你应该在任何基本的 Haskell 文本中阅读这一点。简而言之,newtype X a = Y (...) 创建了一个新的 type 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 = {- ... -}

注意(>>=) 的实现有点冗长,因为您需要使用newtype 的数据构造函数M 解包和重新包装。看看the implementation of StateT in transformers,它使用了一个记录访问器来使它更容易。 (您可以手动编写一个函数runM :: M -> State -> (a, State) 等效于transformers 和许多其他包使用的记录语法。)

【讨论】:

  • 这是否也适用于newtype M a = (State -> (a, State)),或者newtype 在某种程度上不同于type,因此需要在右侧添加M
  • @DavidPfau newtype 不同。您必须使用 M 构造函数显式包装和解包事物,因为您正在创建一个不会作为包装类型进行类型检查的新类型。
【解决方案2】:

这是一个实现:

    -- 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"

【讨论】:

    猜你喜欢
    • 2023-03-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-03-12
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多