Haskell:实现简单的Monad案例研究的问题

时间:2014-11-27 03:45:48

标签: haskell monads

我通过实现一个简单的例子开始研究Monads,但是我的Monad实例没有编译。

我想做类似的事情:

add5 7 >>= add7

此代码必须返回 19 [(5 + 7)>> =(12 + 7)]

我实施的代码是:

newtype MyType a = MyType ( a -> a)

instance Monad MyType where
    MyType comm  >>= comm2  = MyType (\inp -> let
                                        value = comm inp 
                                        MyType comm2' = comm2
                                        in  comm2' value)
    return  x       = MyType (\input -> input)


add5 :: MyType Integer
add5 = MyType (\inp -> inp + 5)

add7 :: MyType Integer
add7 = MyType (\inp -> inp + 7)

当我在不使用绑定运算符的情况下调用add5add7时(通过注释Monad实例块),它可以工作:

main =  do 
        let MyType x = add5
        let MyType y = add7
        putStrLn $ show $ x $ y 7

输出错误是:

new1.hs:5:94:
    Couldn't match expected type `a' with actual type `b'
      `a' is a rigid type variable bound by
          the type signature for
            >>= :: MyType a -> (a -> MyType b) -> MyType b
          at new1.hs:4:9
      `b' is a rigid type variable bound by
          the type signature for
            >>= :: MyType a -> (a -> MyType b) -> MyType b
          at new1.hs:4:9
    In the first argument of `comm', namely `inp'
    In the expression: comm inp
    In an equation for `value': value = comm inp

new1.hs:6:97:
    Couldn't match expected type `MyType t0'
                with actual type `a -> MyType b'
    In the expression: comm2
    In a pattern binding: MyType comm2' = comm2
    In the expression:
      let
        value = comm inp
        MyType comm2' = comm2
      in comm2' value

4 个答案:

答案 0 :(得分:4)

它不能是Monad,因为它甚至不是Functor,因为你在逆变位置有一个类型变量。

这意味着你无法实现:

fmap :: (a->b)->MyType a->MyType b

您可以使用f :: a->ba->a中的结果类型更改为a->b,但您无法将该参数的类型更改为b->b 1}},这是构建MyType b所必需的。

答案 1 :(得分:4)

我不确定你真正想做什么。如果您只是想获取代码示例

add5 7 >>= add7

通过以“明显”方式添加数字来工作并生成19的结果,然后它很简单,并且任何 monad都可以。因此,我们可以选择最简单的monad,即“身份”monad:

newtype Id a = Id { runId :: a }

instance Monad Id where
  return x   = Id x
  Id x >>= f = f x

请注意,此代码将在GHC 7.8中生成警告,因为将来Applicative将成为monad的超类,您将不得不定义其他实例。对于这个例子,它们是无关紧要的,所以我将省略它们。

现在您可以定义add5add7

add5 :: Id Int
add5 n = return (n + 5)

add7 :: Id Int
add7 n = return (n + 7)

如果省略类型签名并询问GHCi,您会发现两个定义实际上都具有更通用的类型(Num a, Monad m) => a -> m a。这就是我的意思,说你的例子适用于任何 monad。

您可以尝试在GHCi中使用它:

GHCi> :t add5 7 >>= add7 
add5 7 >>= add7 :: Id Int
GHCi> runId (add5 7 >>= add7)
19

答案 2 :(得分:3)

你走错了路。 MyType不能成为monad。 MyType唯一可能的monad实例实现将非常简单,并且无法使add5 7 >>= add7等于19

>>=必须有

类型
MyType a -> (a -> MyType b) -> MyType b -- remove newType
(a -> a) -> (a -> (b -> b)) -> (b -> b)

typechecks的唯一功能是

(MyType _) >>= _  = MyType (\input -> input)

与您的return实施非常相似。我们通常在haskell中写id而不是(\input -> input)

为什么我声称这是唯一的功能? 再次检查简化类型签名(a -> a) -> (a -> (b -> b)) -> (b -> b):如果没有输入a,则无法评估>>=a -> aa -> (b -> b)的参数。

这不符合monad法律:x >>= return = x

MyType (\x -> x + 1) >>= return
 =MyType id
/=MyType (\x -> x + 1) 

答案 3 :(得分:1)

你不需要monad来做你想做的事情。相反,您可以使用Arrows

Prelude> :m + Control.Arrow
Prelude Control.Arrow> let add5 = (+5)
Prelude Control.Arrow> let add7 = (+7)
Prelude Control.Arrow> let add12 = add5 >>> add7
Prelude Control.Arrow> add12 7
19

对于函数实例,(>>>)函数只是组合运算符(即(.)),其参数被翻转。因此你可以这样做:

Prelude> let add5 = (+5)
Prelude> let add7 = (+7)
Prelude> let add12 = add7 . add5
Prelude> add12 7
19

已经有一个函数的monad实例。它被称为读者monad:What is the purpose of the Reader Monad?

instance Monad ((->) r) where
    return x = \_ -> x
    f >>= g = \x -> g (f x) x

它允许你这样的事情:

a2-minus-b2 = \a -> do
    a-minus-b <- (\b -> a - b)
    a-plus-b  <- (\b -> a + b)
    return (a-minus-b * a-plus-b)

当然,最好把它写成:

a2-minus-b2 = \a b -> (a - b) * (a + b)

但是,我只想告诉你读者monad可以用来做什么。