如果我这样定义“绑定”函数:
(>>=) :: M a -> (a -> M' b) -> M' b
如果我希望结果是新的 Monad 类型,或者我应该使用相同的 Monad 但 b 和以前一样在同一个 Monad 框中,这个定义会对我有帮助吗?
正如我在评论中提到的,我认为不能为一般单子安全地定义这样的操作(例如M = IO
, M' = Maybe
)。
但是,如果 M 可以安全地转换为 M',那么这个绑定可以定义为:
convert :: M1 a -> M2 a
...
(>>=*) :: M1 a -> (a -> M2 b) -> M2 b
x >>=* f = convert x >>= f
反之,
convert x = x >>=* return
一些这样的安全转换方法是maybeToList
(Maybe → []), listToMaybe
([] → Maybe), stToIO
(ST RealWorld → IO), ... 请注意,没有convert
任何 monad 的通用方法。
该定义不仅无济于事,而且会严重混淆您代码的未来读者,因为它会打破所有使用它的期望。
例如,M 和 M' 都应该是 Monad 吗?如果是这样,那么它们是如何定义的?请记住: 的定义>>=
是 Monad 定义的一部分,并且在任何地方都用于定义其他使用 Monad 的函数——除了它们之外的每个return
函数fail
。
另外,您可以选择使用哪个 M 和 M',还是计算机?如果是这样,那你如何选择?它是否适用于任何两个 Monad 实例,或者是否存在您想要的 Monad 的某个子集 - 或者 M 的选择决定了 M' 的选择?
可以像你写的那样做一个函数,但它肯定比复杂得多>>=
,而且试图把你的函数塞进>>=
' 的衣服中是误导、残忍的,并且可能是灾难性的。
这可能是一件复杂的事情,但在某些情况下是可行的。基本上,如果它们是您可以在内部看到的 monad(例如Maybe
您编写的 monad),那么您可以定义这样的操作。
有时非常方便的一件事(在 GHC 中)是Monad
用您自己的类替换该类。如果您定义return, >>=, fail
,您仍然可以使用do
符号。这是一个可能像您想要的示例:
class Compose s t where
type Comp s t
class Monad m where
return :: a -> m s a
fail :: String -> m a
(>>=) :: (Compose s t) => m s a -> (a -> m t b) -> m (Comp s t) b
(>>) :: (Compose s t) => m s a -> m t b -> m (Comp s t) b
m >> m' = m >>= \_ -> m'
然后,您可以根据定义的实例使用绑定运算符控制可以对哪些类型进行排序Compose
。自然你会经常想要Comp s s = s
,但你也可以用它来定义各种疯狂的东西。
例如,也许您的 monad 中有一些绝对不能被任何其他操作遵循的操作。想要静态执行吗?定义一个空数据类型data Terminal
并且不提供Compose Terminal t
.
这种方法不适合从 (say) Maybe
to转置IO
,但它可以用来携带一些关于你正在做什么的类型级数据。
如果您确实想更改 monad,可以将上面的类定义修改为类似
class Compose m n where
type Comp m n
(>>=*) :: m a -> (a -> n b) -> (Compose m n) b
class Monad m where
return :: a -> m a
fail :: String -> m a
(>>=) :: Compose m n => m a -> (a -> n b) -> (Compose m n) b
m >>= f = m >>=* f
(>>) :: Compose m n => m a -> (n b) -> (Compose m n) b
m >> n = m >>=* \_ -> n
我已经将前一种风格用于有用的目的,尽管我认为后一种想法在某些情况下也可能有用。
您可能想查看 Oleg 的此示例:http: //okmij.org/ftp/Computation/monads.html#param-monad