我被困在这里,我可以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的一个实例?
答案 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
在上面的代码中自动执行。不过,这很无聊,也很麻烦。但是,它不需要类型注释或参数,所以至少它会节省那些努力。