如何使newtype成为monad的一个实例

时间:2017-12-31 02:21:33

标签: haskell

我被困在这里,我可以data类型Monad的实例,但我无法弄清楚如何使用newtype来实现这一点。

newtype Val a = Val {getVal :: [a]} deriving (Show)

instance Monad Val where
    return = Val
    (>>=) (Val {getVal = l}) f  = map f l

如果我这样写:

instance Monad Val where
return = Val
(>>=) (Val {getVal = l}) f  =  {getVal = map f l}

然后我收到错误,它说parse error on input {

如何让newtype成为Monad的一个实例?

3 个答案:

答案 0 :(得分:5)

这里有几个问题。首先,您必须在大括号之前指定记录名称。所以你想要

(>>=) (Val {getVal = l}) f = Val {getVal = map f l}

现在,你的任何一个函数都不会立即进行类型检查。但修复并不太难,因为您只是委托已经存在的列表monad实例。您的return已关闭,但您需要将结果包装在列表中。

return x = Val [x]

同样,您不希望记录中包含map。您需要(>>=)的列表monad版本。

(>>=) (Val {getVal = l}) f = Val {getVal = l >>= f}

不幸的是,这仍然不会完全类型检查,因为f旨在返回Val而不是[]。我们需要在monad操作中进行修正。

(>>=) (Val {getVal = l}) f = Val {getVal = l >>= getVal . f}

之后,您可能会收到Applicative作为Monad超类的错误。这可以通过一些辅助函数的简单应用来解决。

import Control.Monad

-- ...

instance Functor Val where
    fmap = liftM

instance Applicative Val where
    pure = return
    (<*>) = ap

现在一切都应该编译。

作为旁注,如果你正在使用GHC,那么有一个名为GeneralizedNewtypeDeriving的便捷功能可以自动为你提供newtype数据类型的实例。您可以像这样使用它

{-# LANGUAGE GeneralizedNewtypeDeriving #-}

newtype Val a = Val {getVal :: [a]}
    deriving (Show, Functor, Applicative, Monad)

这仅适用于newtype,而不适用于data,并且它仅适用于GHC,因此如果您希望代码可以跨编译器移植,请不要使用它。

答案 1 :(得分:3)

Val的定义中构建>>=的实例时,您需要提供构造函数:

(>>=) (Val {getVal = l}) f = Val {getVal = map f l}

Haskell没有“免费”记录,就像PureScript那样。在Haskell中,{ x = y, v = u }本身并不是构造记录的有效语法。您需要始终提供构造函数,例如C { x = y, v = u }

答案 2 :(得分:1)

GeneralizedNewtypeDeriving是这里最方便的选择。

更长的选择是使用安全强制,以便让Haskell自动执行所有包装/解包。不幸的是,这需要一些手动类型的注释和参数,因为否则类型对于自动强制工作来说太普遍了。

不是非常方便,但确实如此。

{-# LANGUAGE InstanceSigs, ScopedTypeVariables, TypeApplications #-}

import Data.Coerce

newtype Val a = Val {getVal :: [a]} deriving (Show)

instance Functor Val where
   fmap :: forall a b. (a -> b) -> Val a -> Val b
   fmap = coerce (fmap @ [] @ a @ b)

instance Applicative Val where
   pure :: forall a. a -> Val a
   pure = coerce (pure @ [] @ a)
   (<*>) :: forall a b. Val (a -> b) -> Val a -> Val b
   (<*>) = coerce ((<*>) @ [] @ a @ b)

instance Monad Val where
   return :: forall a. a -> Val a
   return = coerce (return @ [] @ a)
   (>>=) :: forall a b. Val a -> (a -> Val b) -> Val b
   (>>=) = coerce ((>>=) @ [] @ a @ b)

另一种选择是手动执行换行/解包,coerce在上面的代码中自动执行。不过,这很无聊,也很麻烦。但是,它不需要类型注释或参数,所以至少它会节省那些努力。