ghci> :t (>>=)
(>>=) :: Monad m => m a -> (a -> m b) -> m b
Run Code Online (Sandbox Code Playgroud)
为什么第二个论点(a -> m b)不是(m a -> m b)甚至是(a -> b)?这是什么概念有关单子需要此签名?具有替代签名的类型类是否有意义t a -> (t a -> t b) -> t b.t a -> (a -> b) -> t b?
fuz*_*fuz 16
monad的更对称定义是Kleisli组合子,它基本上(.)用于monad:
(>=>) :: (a -> m b) -> (b -> m c) -> (a -> m c)
Run Code Online (Sandbox Code Playgroud)
它可以取代(>>=)monad的定义:
f >=> g = \a -> f a >>= g
a >>= f = const a >=> f $ ()
Run Code Online (Sandbox Code Playgroud)
Stu*_*ook 12
这是在Haskell通常定义Monad在以下方面return和(>>=):
class Monad m where
(>>=) :: m a -> (a -> m b) -> m b
return :: a -> m a
Run Code Online (Sandbox Code Playgroud)
但是,我们可以使用这个等价的定义,它更接近原始的数学定义:
class Monad m where
fmap :: (a -> b) -> m a -> m b
join :: m (m a) -> m a
return :: a -> m a
Run Code Online (Sandbox Code Playgroud)
正如你所看到的那样,不对称性(>>=)已被不对称性所取代,这种不对称性join将m (m a)"两层" m变为"正常" m a.
您还可以看到fmap匹配的签名t a -> (a -> b) -> t b,但参数相反.这是特征化类型类的操作Functor,它严格地弱于Monad:每个monad都可以成为一个仿函数,但并不是每个仿函数都可以成为一个monad.
这在实践中意味着什么?好吧,当转换只是一个仿函数的东西时,你可以fmap用来转换仿函数"内"的值,但这些值永远不会影响仿函数本身的"结构"或"效果".然而,对于monad,这种限制被解除了.
作为一个具体的例子,当你这样做时fmap f [1, 2, 3],你知道无论如何f,结果列表将有三个元素.但是,当您这样做时[1, 2, 3] >>= g,可以g将这三个数字中的每一个转换为包含任意数量值的列表.
同样,如果我这样做fmap f readLn,我知道它除了读取一行之外不能执行任何I/O操作.如果我这样做readLn >>= g,在另一方面,有可能为g检查所读取的值,然后用它来决定是否打印出一个消息,或者阅读ñ更多的线,或做其他任何内可能IO.
Car*_*ten 11
Brian Beckman在(在我看来)对monad的精彩介绍中给出了一个非常好的答案:不要害怕Monad
你也可以看看"了解你一个哈克尔 "这个很好的章节:一个充满莫纳德的东西.这也很好地解释了它.
如果你想要务实:它必须是这样才能让'do'-laague具功能继续下去;) - 但Brian和Lipovaca解释它比那更好(和更深);)
PS:对你的选择:第一个或多或少是第二个参数的应用到第一个.第二种选择几乎fmap是Functor类型 - 只有切换参数(并且每个Monad都是一个Functor - 即使Haskell类型类没有约束它 - 但它应该 - 但这是另一个主题;) )
C. *_*ann 10
嗯,这种类型(>>=)对于desugaring do符号很方便,但在某种程度上不自然.
目的(>>=)是在monad中使用一个类型,并使用该类型的参数在monad中创建其他类型的函数,然后通过提升函数并展平额外的图层来组合它们.如果查看join函数Control.Monad,它只执行展平步骤,所以如果我们将它作为基本操作,我们可以这样写(>>=):
(>>=) :: (Monad m) => m a -> (a -> m b) -> m b
m >>= k = join (fmap k m)
Run Code Online (Sandbox Code Playgroud)
但请注意,参数的反转顺序为fmap.如果我们考虑Identitymonad,这个原因就变得清晰了,monad只是普通值的新类型包装器.忽略newtypes,fmapfor Identity是函数应用程序并且join什么都不做,所以我们可以认为(>>=)它是一个应用程序操作符,它的参数被反转.比较此运算符的类型,例如:
(|>) :: a -> (a -> b) -> b
x |> f = f x
Run Code Online (Sandbox Code Playgroud)
一个非常相似的模式.因此,为了更清楚地了解(>>=)类型的含义是什么,我们将改为查看(=<<),其中定义了Control.Monad,它以其他顺序获取其参数.比较它(<*>),从Control.Applicative,fmap和($),并记住这(->)是正确的关联,并添加多余的括号:
($) :: (a -> b) -> ( a -> b)
fmap :: (Functor f) => (a -> b) -> (f a -> f b)
(<*>) :: (Applicative f) => f (a -> b) -> (f a -> f b)
(=<<) :: (Monad m) => (a -> m b) -> (m a -> m b)
Run Code Online (Sandbox Code Playgroud)
因此,所有这四个本质上都是函数应用程序,后三个是"提升"函数以处理某些函数类型的值的方法.它们之间的差异对于简单值Functor以及基于它的两个类如何不同至关重要.从宽松的意义上讲,类型签名可以解读如下:
fmap :: (Functor f) => (a -> b) -> (f a -> f b)
Run Code Online (Sandbox Code Playgroud)
这意味着给定一个普通函数a -> b,我们可以将它转换为在类型f a和函数上执行相同操作的函数f b.因此,它只是一个简单的转换,不能改变或检查结构f,无论它是什么.
(<*>) :: (Applicative f) => f (a -> b) -> (f a -> f b)
Run Code Online (Sandbox Code Playgroud)
就像fmap,除了它需要一个本身已经存在的函数类型f.函数类型仍然无视结构f,但在某种意义上(<*>)它本身必须结合两个f结构.因此,这可以改变和检查结构,但仅以结构本身确定的方式,独立于值.
(=<<) :: (Monad m) => (a -> m b) -> (m a -> m b)
Run Code Online (Sandbox Code Playgroud)
这是一个深刻的,根本性的转变,因为现在我们采用一个创建一些m结构的函数,它与已经存在于m a参数中的结构相结合.因此,(=<<)不仅可以改变上面的结构,而且提升的功能可以根据值创建新的结构.但是仍然存在一个很大的局限性:该函数只接收一个普通值,因而无法检查整体结构; 它只能检查一个位置,然后决定放在那里的结构类型.
那么,回到你的问题:
具有替代签名的类型类是否有意义
t a -> (t a -> t b) -> t b.t a -> (a -> b) -> t b?
如果你按照上面的"标准"顺序重写这两种类型,你可以看到第一种只是($)一种特殊的类型,而第二种是fmap.有是有意义其他变化,但是!这里有几个例子:
contramap :: (Contravariant f) => (a -> b) -> (f b -> f a)
Run Code Online (Sandbox Code Playgroud)
这是一个逆向函子,它"向后"工作.如果类型一开始看起来不可能,请考虑类型newtype Flipped b a = Flipped (a -> b)以及您可以使用它做什么.
(<<=) :: (Comonad w) => (w a -> b) -> (w a -> w b)
Run Code Online (Sandbox Code Playgroud)
这是monad的双重 - 而参数(=<<)只能检查局部区域并产生一块结构放在那里,(<<=)可以检查全局结构并产生汇总值的参数.(<<=)本身通常在某种意义上扫描结构,从每个视角获取汇总值,然后重新组合它们以创建新结构.
m a -> (a -> b) -> m b是行为Functor.fmap,这是非常有用的.然而它比...更有限>>=.例如,如果您处理列表,fmap可以更改元素及其类型,但不能更改列表的长度.另一方面,>>=可以轻松地做到这一点:
[1,2,3,4,5] >>= (\x -> replicate x x)
-- [1,2,2,3,3,3,4,4,4,4,5,5,5,5,5]
Run Code Online (Sandbox Code Playgroud)
m a -> (m a -> m b) -> m b不是很有趣.这只是$具有反向参数的函数应用程序(或):我有一个函数m a -> m b并提供一个参数m a,然后我得到m b.
[编辑]
奇怪的是,没有人提到第四个可能的签名:m a -> (m a -> b) -> m b.这实际上也有意义,并导致Comonads
| 归档时间: |
|
| 查看次数: |
786 次 |
| 最近记录: |