Monad比Applicative更强大?

rap*_*apt -4 monads haskell functional-programming functor applicative

我查看过去的讨论,但无法理解为什么任何答案都是正确的.

合用的

<*> :: f (a -> b) -> f a -> f b

单子

(>>=) :: m a -> (a -> m b) -> m b

因此,如果我做对了,那么声称是>>=不能仅仅假设存在而写的<*>

好吧,让我们假设我有<*>.

我想创造>>=.

所以我有f a.

我有f (a -> b).

现在当你看它时,f (a -> b)可以写成(a -> b)(如果某个东西是x,y,z的函数 - 那么它也是x,y的函数).

所以从<*>我们得到的存在(a -> b) -> f a -> f b再次可以写成((a -> b) -> f a) -> f b,可以写成(a -> f b).

所以我们拥有f a,(a -> f b)而且我们知道<*>结果f b,所以我们得到:

f a -> (a -> f b) -> f b

这是一个单子.

其实,在一个更直观的语言:在实现时<*>,我中提取(a -> b)出来的f(a -> b),我提取a出来f a,然后我申请(a -> b)a,让b我与包裹f终于得到f b.

所以我做的几乎一样>>=.应用后(a -> b)a和得到b,做一个步骤,并把它包f,所以我回来f b,所以我知道我有一个函数(a -> f b).

Zet*_*eta 12

现在当你看它时,f(a -> b)可以写成(a -> b)

不,它不能.在这一点上,你的直觉(危险地)远远不够.这就像说锤子是完美的驱动螺钉,因为它已经适用于钉子*.你不能简单地f放在这里,它是**类型的一部分.

相反,让我们直截了当地了解事实.一个Applicative有三个相关的功能,计算Functorfmap:

fmap  :: Functor f     =>   (a -> b) -> f a -> f b
pure  :: Applicative f =>                 a -> f a
(<*>) :: Applicative f => f (a -> b) -> f a -> f b
Run Code Online (Sandbox Code Playgroud)

这是另一个事实:您可以根据方式定义bind((>>=)),join反之亦然:

join :: Monad m => m (m a) -> m a
join k = k >>= id

(>>=) :: Monad m => m a -> (a -> m b) -> m b
k >>= f = join (fmap f k)
Run Code Online (Sandbox Code Playgroud)

你在这里提供的加入和绑定的实现是Monad定义的一部分,还是只是加入和绑定Monad定义的一部分?[...]所以现在我问自己他们为什么要打扰.

这些当然不是官方定义,因为它们永远不会终止.(>>=)如果你想让它成为一个monad,你必须为你的类型定义:

instance Monad YourType where
   k >>= f = ...
Run Code Online (Sandbox Code Playgroud)

此外,您的连接定义使用不在Monad界面中的id,为什么它在数学上是合法的?

首先,id :: a -> a定义为任何类型.其次,monad的数学定义实际上是通过join.所以它"更"合法.但最重要的是,我们可以用join(运动)来定义monad定律.

如果我们创建了joinvia Applicative,我们也可以创建bind.如果我们join不能通过Applicative方法创建,我们也不能得出bind.而且join它的类型实际上很明显我们无法从中得出它Applicative:

join :: Monad m => m (m a) -> m a
             --    ^  ^       ^
Run Code Online (Sandbox Code Playgroud)

Join可以删除其中一个m图层.让我们检查是否可以在其他方法中执行相同的操作:

fmap  :: Functor f     =>   (a -> b) -> f a -> f b
                          ^                    ^
                        0 here              1 here
Run Code Online (Sandbox Code Playgroud)
pure  :: Applicative f =>                 a -> f a
                                          ^  | ^
                                      0 here | 1 here
Run Code Online (Sandbox Code Playgroud)
(<*>) :: Applicative f => f (a -> b) -> f a -> f b
                          ^                    ^
                       1 here                1 here
Run Code Online (Sandbox Code Playgroud)

答案是否定的:我们所提供的工具都不Applicative能让我们将多个工具折叠m成一个工具.这也是在另一个问题引用的段落之后的Typeclassopedia中写的内容:

要查看从不同的角度单子增加的动力,让我们看看,如果我们试图实现什么情况(>>=)来讲fmap,pure(<*>).我们都获得了价值xm a和功能k型的a -> m b,所以我们能做的唯一事情就是申请kx.当然,我们不能直接申请; 我们必须用fmap它来举起它m.但是什么类型的fmap k?嗯,是的m a -> m (m b).因此,在我们应用它之后x,我们留下了类型的东西m (m b)- 但现在我们被困住了; 我们真正想要的是一个m b,但是没有办法从这里到达那里.我们可以添加m使用pure,但我们无法将多个数据m合并为一个.

请注意,join这不能m完全摆脱,这将是一个完全提取,并取决于一些其他功能 - comonad的功能.无论哪种方式,确保你不要让你的直觉误入歧途; 信任并使用这些类型.

*这种比较有点不好,因为你实际上可以尝试用锤子将螺钉拧入一块木头.所以想想你想要钉入的塑料螺丝,橡胶锤和碳钢板.祝你好运.

**嗯,你可以放弃它,但随后类型会发生巨大变化.

***鉴于(>>=)并且join相当于力量,任何(>>=)使用公式都可以转换为仅使用join,它们当然都是数学声音.

  • 我认为你误解的根源是这样的:`f(a - > b)`实际上不是一个`a - > b`包裹在`f`中.它不是"有锤子的房子".这与*a - > b`*相关*,但它可能包含也可能不包含.有很好的应用程序,有时不包含其类型参数的值,包含其中许多,包含从不包含的其他信息生成它们的方法,甚至*从不*包含这样的值.你有一个房子,上面有一把锤子的图片,门被锁上了. (4认同)
  • @rapt:另外,使用`id`是可以的,因为它已经定义了:`id :: a - > a`; `id x = x`.因为`id`可以应用于*any*type`a`,所以总是可以使用它. (2认同)

J. *_*son 10

现在当你看它时,f (a -> b)可以写成(a -> b)

每个人都已经做出了解释,这不是事实.让我向你证明一下.

如果我们真的拥有你陈述的内容,那么我们应该能够编写一个函数

expose :: f (a -> b) -> (a -> b)
Run Code Online (Sandbox Code Playgroud)

而且,对于我们喜欢的任何具体数据类型,请调用它F,我们应该能够编写

expose_F :: F (a -> b) -> (a -> b)
expose_F = expose
Run Code Online (Sandbox Code Playgroud)

让我们只担心写作,expose_F因为如果我们可以表明expose_F不能写一些,F那么我们肯定已经证明expose无法写.


让我给我们一个测试F.这肯定是非直觉的感觉,因为我打算用它来打破直觉,但我很高兴终日确认没有任何有趣的事情.

data F a = F
Run Code Online (Sandbox Code Playgroud)

确实,这是一个 Functor

instance Functor F where
  fmap _ F = F
Run Code Online (Sandbox Code Playgroud)

并且Applicative就此而言

instance Applicative F where
  pure _ = F
  F <*> F = F
Run Code Online (Sandbox Code Playgroud)

甚至一个 Monad

instance Monad F where
  return _ = F
  F >>= _ = F
Run Code Online (Sandbox Code Playgroud)

您可以验证所有这些类型检查.完全没有错F.

那么什么是正确的,F?我为什么选择它?那么F是的,价值观有趣F a无法包含所有相关的任何东西a在其中.通常人们喜欢将数据类型(或Functors)称为"容器"或"盒子".F迫使我们记住,在某种意义上,一个0英寸深的盒子仍然是一个盒子.[0]

所以我们肯定不会写

expose_F :: F (a -> b) -> (a -> b)
Run Code Online (Sandbox Code Playgroud)

有很多方法可以证明这一点.最简单的是吸引我的假设,例如,你相信没有任何coerce功能.但是,如果我们有,expose_F那就会!

coerce :: a -> b
coerce = expose_F F
Run Code Online (Sandbox Code Playgroud)

更具体地说,让我介绍另一种病理类型(我再次向你保证完全没问题)

data Void
Run Code Online (Sandbox Code Playgroud)

零名的建设者Void,我们愿意因此说,Void没有成员.它不可能存在.但我们可以制作一个expose_F.

void :: Void
void = expose_F F ()
Run Code Online (Sandbox Code Playgroud)

在Haskell中,我们在技术上没有足够的声音来执行上述证明.如果你不喜欢我谈论不可能性的方式那么你可以用一个方便的无限循环来召唤你喜欢的任何类型,随便打电话给

 error "Madness rides the star-wind... claws and teeth sharpened on centuries of corpses... dripping death astride a bacchanale of bats from nigh-black ruins of buried temples of Belial..."
Run Code Online (Sandbox Code Playgroud)

或许是不张扬的undefined.但这些都在疯狂的道路上.


没有expose_F,因此没有expose.


[0]并且要完全清楚,将数据类型视为盒子通常是有缺陷的.实例Functor往往是"类似盒子",但这是另一种有趣的数据类型,很难将其视为一个盒子

data Unbox a = Unbox (a -> Bool)
Run Code Online (Sandbox Code Playgroud)

除非你认为Unbox a是一个包含一个Bool和一个否定 a或类似的东西的盒子.也许是IOU a.