我总是喜欢以下关于monad相对于仿函数的力量的直观解释:monad可以改变形状; 算子不能.
例如:length $ fmap f [1,2,3]
总是等于3
.
然而,使用monad length $ [1,2,3] >>= g
通常不会相等3
.例如,if g
定义为:
g :: (Num a) => a -> [a]
g x = if x==2 then [] else [x]
Run Code Online (Sandbox Code Playgroud)
那就[1,2,3] >>= g
等于[1,3]
.
麻烦我的东西,是类型签名g
.似乎不可能定义一个改变输入形状的函数,具有通用的monadic类型,例如:
h :: (Monad m, Num a) => a -> m a
Run Code Online (Sandbox Code Playgroud)
MonadPlus或MonadZero类型具有相关的零元素,而不是使用[]
,但现在我们拥有的不仅仅是monad.
我对么?如果是这样,有没有办法向Haskell的新人表达这种微妙之处.我想让我心爱的"monad可以改变形状"这句话,只需要更加诚实; 如果需要的话.
C. *_*ann 16
我总是喜欢以下关于monad相对于仿函数的力量的直观解释:monad可以改变形状; 算子不能.
顺便说一下,你在这里错过了一点微妙.为了术语,我将Functor
Haskell意义上的a分为三个部分:由类型参数确定并由其操作的参数组件fmap
,诸如元组构造函数之类的不变部分State
以及作为其他任何内容的"形状" ,例如构造函数之间的选择(例如,Nothing
vs. Just
)或涉及其他类型参数的部分(例如,环境Reader
).
当然,Functor
单独限于在参数部分上映射函数.
A Monad
可以根据参数化部分的值创建新的"形状" ,这不仅可以改变形状.复制列表中的每个元素或删除前五个元素都会改变形状,但过滤列表需要检查元素.
这基本上是Applicative
它们之间的契合度 - 它允许您独立地组合两个的形状和参数值Functors
,而不会让后者影响前者.
我对么?如果是这样,有没有办法向Haskell的新人表达这种微妙之处.我想让我心爱的"monad可以改变形状"这句话,只需要更加诚实; 如果需要的话.
也许你在这里寻找的微妙之处在于你并没有真正"改变"任何东西.什么都没有Monad
让你明确地弄乱形状.它允许您做的是根据每个参数值创建新形状,并将这些新形状重新组合成新的复合形状.
因此,您将始终受到创建形状的可用方法的限制.Monad
所有你拥有的完全通用return
,根据定义创建任何必要的形状,如(>>= return)
身份功能.a的定义Monad
告诉你在给定某些函数的情况下你可以做什么; 它不会为您提供这些功能.
Monad
操作可以"改变值的形状"到>>=
函数替换"树"中叶节点的程度,该树是原始值,具有从节点的值派生的新子结构(对于"树"的适当的一般概念 -在列表的情况下,"树"是关联的).
在您的列表示例中,正在发生的事情是每个数字(叶子)正在被g
应用于该数字时产生的新列表替换.如果你知道你在寻找什么,那么原始列表的整体结构仍然可以看到; 结果g
仍然存在,它们只是被打碎在一起所以除非你已经知道,否则你无法分辨出哪一个结束而另一个结束.
更具启发性的观点可能是考虑fmap
而join
不是>>=
.与之相伴return
,任何一种方式都给出了monad的等价定义.但是在fmap
/ join
view中,这里发生的事情更加明确.继续列表示例,首先g
是fmap
在列表中产生[[1],[],[3]]
.然后join
编辑该列表,列表就是concat
.
仅仅因为monad模式包含一些允许形状变化的特定实例并不意味着每个实例都可以进行形状变化.例如,Identity
monad中只有一个"形状" :
newtype Identity a = Identity a
instance Monad Identity where
return = Identity
Identity a >>= f = f a
Run Code Online (Sandbox Code Playgroud)
事实上,目前还不清楚,我认为很多单子有有意义的"形" S:例如,什么形状的意思是State
,Reader
,Writer
,ST
,STM
,或IO
单子?