Sen*_*agi 25 monads haskell functional-programming arrows
所以我将我的问题分成4个部分,但首先是一些背景:
我对Monads感觉相对舒服,但对Arrows不太满意.我想我遇到的主要问题是,我看不出它们对它们有用.无论形式是否正确,我都理解Monads是一种工具,可以让我们从计算中引入副作用.因为它们将程序片段从纯值推广到用其他动作装箱的值.从我的霰弹枪"阅读所有论文"的方法来学习箭头,我遇到了两个相互矛盾的观点:
答:箭头比Monads更强大/是Monads的概括.haskell wiki的开头是"他们可以做monad所能做的一切,甚至更多.它们与具有静态组件的monad大致相当."
B.箭头是Monads的子集使用ArrowApply我们可以定义monad
And*_*ewC 41
可解释的语句提醒:
"A比B更强大"......"C是D的概括"......"E可以做F所能做的一切,更多"......"G是H的子集"......
首先,我们需要掌握强大的东西.我们假设我们有一个GripHandle
用于握把手的东西,另一个Screwdriver
用于螺丝刀的类.哪个更强大?
好吧,这是一个愚蠢的论点,但它提出了一个很好的观点,即"你可以用_____做更多"这句话是多么含糊不清.
如果你坚持到接口孤单,一个螺丝刀比更有用一个手柄,但都与griphandles事情比起来更有用的所有螺丝刀如果你使用更多的功能不仅仅是界面.
A
更通用的是B
= A
'仅限接口的能力是B
's =你可以用一个B
实例做更多的一个子集(单独)
=所有B
s 的类是所有类的一个子集A
s =有多个A
s而不是B
s你可以在A
课堂上 做更多的事情
更一般
=更多可能的实例
=能够被更广泛地使用
=可以在幕后做额外的事情
=在界面中指定更少的能力
=可以通过界面做更少的事情
Arrow
比一般更普遍Monad
.ArrowApply
就像一般一般Monad
.这两个陈述在PetrPudlák在他的评论中所附的文章中得到了充分的详细证明:成语是不经意的,箭头是细致的,monad是混杂的.
ArrowApply
实例本身允许你自己做一个Monad
实例.你不能ArrowApply
用a 做更多Monad
.你可以做更多的事情Arrows
.声称"monad所能做的一切"可能指的是ArrowApply
声称"和更多"可能指的是Arrow
!Monad营销委员会可以说"有了Monads,你可以做所有箭头可以做的事情,而且更多",因为界面的表现力增强了.这些陈述含糊不清,因此具有很少的正式含义.Arrow
,你不能直接用a做Monad
,而不是关于Arrow界面的数学事实.这是一种帮助你掌握我们可能用箭头操作的方法,类似于Monad是一个有价值的盒子的类比.并非所有的monad都可以解释为具有值的盒子,但它可能会帮助你在早期阶段.Arrow
是比较一般.箭头没有什么样的功能,我读过它与组合有什么区别,那么>>>运算符允许我们做什么>> =不?
事实上>>=
,您可以做到更多>>>
(更多接口提供的功能).它允许您进行上下文切换.这是因为Monad m => a -> m b
是一个函数,所以你可以a
在决定运行哪个monadic之前执行输入上的任意纯代码,而Arrow m => m a b
不是函数,并且你已经决定在检查输入之前要运行哪个箭头a
.
monadSwitch :: Monad m => m a -> m a -> (Bool -> m a)
monadSwitch computation1 computation2 test
= if test then computation1 else computation2
Run Code Online (Sandbox Code Playgroud)
Arrow
不使用app
from 就无法模拟这种情况ArrowApply
应用究竟做了什么?它的类型甚至没有( - >)
它允许你使用箭头的输出作为箭头.我们来看看类型.
app :: ArrowApply m => m (m b c, b) c
Run Code Online (Sandbox Code Playgroud)
我更喜欢使用m
,a
因为m
感觉更像是一个计算,a
感觉就像一个价值.有些人喜欢使用类型操作符(中缀类型构造函数),所以你得到了
app :: ArrowApply (~>) => (b ~> c, b) ~> c
Run Code Online (Sandbox Code Playgroud)
我们认为b ~> c
是一个箭头,我们认为箭头是一个需要b
s,做某事并给出c
s的东西.所以这意味着app
箭头带有一个箭头和一个值,并且可以产生第一个箭头在该输入上产生的值.
它没有->
类型签名,因为在使用箭头编程时,我们可以使用任何函数将箭头转换为箭头arr :: Arrow (~>) => (b -> c) -> b ~> c
,但是您无法将每个箭头转换为函数,因此(b ~> c, b) ~> c
可以在任何地方使用(b ~> c, b) -> c
或(b -> c, b) ~> c
不使用.
我们可以轻松地进行,只是做生产的箭头,甚至多重箭,即使没有ArrowApply,箭头produceArrow :: Arrow (~>) => (b ~> c) -> (any ~> (b ~> c))
与定义produceArrow a = arr (const a)
.困难在于让箭头做任何箭头工作 - 如何让你生成的箭头成为下一个箭头?你不能像下一个计算那样使用>>>
monadic 函数 弹出它Monad m => a -> m b
(只是做id :: m a -> m a
!),因为,至关重要的是,箭头不是函数,而是使用app
,我们可以使下一个箭头执行箭头生成的任何内容由前一个箭头完成.
因此,ArrowApply为您提供了Monad的运行时生成的计算运行性.
为什么我们想要在monad上使用适用的箭头?
呃,你的意思是箭头或应用函数?Applicative Functors很棒.它们比Monad或Arrow更常见(参见论文),因此具有较少的界面指定功能,但更广泛适用(得到它?适用/适用chortle chortle lol rofl类别理论幽默hahahaha).
Applicative Functors具有可爱的语法,看起来非常像纯函数应用程序.f <$> ma <*> mb <*> mc
运行ma
,然后mb
再mc
和纯函数应用f
到三个结果.例如.(+) <$> readLn <*> readLn
从用户读取两个整数并添加它们.
你可以使用Applicative来获得通用性,你可以使用Monads来获得接口功能,所以你可以说理论上我们不需要它们,但有些人喜欢箭头的符号,因为它就像记谱法,而你确实可以Arrow
用来实现具有静态组件的解析器,从而应用编译时优化.我相信你可以用Applicative做到这一点,但是先用Arrow完成.
关于Applicative"不那么强大"的说明:
本文指出Applicative
比一般更为通用Monad
,但是你可以通过提供一个run :: Applicative f => f (f b) -> f b
允许你运行生成计算的函数或者use :: Applicative f => f (a -> f b) -> f a -> f b
允许你提升生产的函数来使Applicative函数具有相同的能力.计算到计算.如果我们定义join = run
并且unit = (<$>)
我们得到两个为Monads构成一个理论基础的函数,并且如果我们定义(>>=) = flip (use.pure)
并且return = unit
我们得到另一个在Haskell中使用的函数.没有一个ApplicativeRun
类,只是因为如果你可以创建它,你可以制作一个monad,类型签名几乎相同.我们没有ArrowApply
重复使用的唯一原因Monad
是类型不相同; ~>
被抽象化(推广)到ArrowApply中的接口,但函数应用程序->
直接在Monad中使用.尽管ArrowApply和Monad等价,但这种区别使得使用Arrows进行编程在monad中进行编程的方式有所不同.
<cough> 为什么我们要在Monad上使用Arrows/ArrowApply?
好吧,我承认我知道这就是你的意思,但是想谈谈Applicative functors并让我们忘掉了,我忘了回答!
能力原因:是的,如果你有一些无法制作成monad的东西,你会想要使用Arrow over Monad.首先给我们带来Arrows的激励示例是解析器 - 您可以使用Arrow编写一个解析器库,在组合器中进行静态分析,从而提高解析器的效率.以前的Monadic解析器不能这样做,因为它们将解析器表示为一个函数,它可以对输入执行任意操作而不会静态记录它们,因此您无法在编译时/组合时分析它们.
句法原因:不,我个人不想使用基于箭头的解析器,因为我不喜欢箭头proc
/ do
符号 - 我发现它比monadic符号更糟糕.我首选的解析器符号是Applicative,你可能会编写一个Applicative解析器库,它可以完成Arrow实现的有效静态分析,虽然我可以自由地承认我常用的解析器库没有,可能是因为他们想要提供Monadic接口.
单子:
parseTerm = do
x <- parseSubterm
o <- parseOperator
y <- parseSubterm
return $ Term x o y
Run Code Online (Sandbox Code Playgroud)箭头:
parseTerm = proc _ -> do
x <- parseSubterm -< ()
o <- parseOperator -< ()
y <- parseSubterm -< ()
returnA -< Term x o y
Run Code Online (Sandbox Code Playgroud)应用型:
parseTerm = Term <$> parseSubterm <*> parseOperator <*> parseSubterm
Run Code Online (Sandbox Code Playgroud)
这看起来像使用$
几次的功能应用程序.MMMMM.整齐.明确.语法低.提醒我为什么我更喜欢Haskell任何命令式编程语言.
在Control.Arrow模块的ArrowApply部分中有一个Monad实例,我将编辑(~>)
而不是a
为了清晰的思路.(我已离开Functor
,因为无论如何定义Monad而没有Functor是愚蠢的 - 你应该定义fmap f xs = xs >>= return . f
.):
newtype ArrowMonad (~>) b = ArrowMonad (() ~> b)
instance Arrow (~>) => Functor (ArrowMonad (~>)) where
fmap f (ArrowMonad m) = ArrowMonad $ m >>> arr f
instance ArrowApply (~>) => Monad (ArrowMonad (~>)) where
return x = ArrowMonad (arr (\_ -> x))
ArrowMonad m >>= f = ArrowMonad $
m >>> arr (\x -> let ArrowMonad h = f x in (h, ())) >>> app
Run Code Online (Sandbox Code Playgroud)
那是做什么的?嗯,首先,ArrowMonad
是一个newtype
类型的同义词而不是类型的同义词,所以我们可以使实例没有各种令人讨厌的类型系统问题,但让我们忽略这一点,通过替换为概念清晰度而不是可编译性type ArrowMonad (~>) b = () ~> b
instance Arrow (~>) => Functor (() ~>) where
fmap f m = m >>> arr f
Run Code Online (Sandbox Code Playgroud)
(使用不可编译的类型操作符部分(()~>)
作为类型构造函数)
instance ArrowApply (~>) => Monad (() ~>) where
-- return :: b -> (() ~> b)
return x = arr (\_ -> x)
-- (>>=) :: ()~>a -> (a -> ()~>b ) -> ()~>b
m >>= f =
m >>> arr (\x -> (f x, ()) ) >>> app
Run Code Online (Sandbox Code Playgroud)
好的,这有点清楚发生了什么.首先注意箭头和monad之间的对应关系是Monad m => b -> m c
和之间Arrow (~>) => b ~> c
,但是monad类不涉及b
声明.这就是为什么我们需要提供虚拟值()
中() ~> b
得到的东西开始的零输入和复制型的东西m b
.
fmap
将函数应用于输出的位置,只是生成输出,然后以箭头形式运行函数:fmap f m = m >>> arr f
x
)只是以const x
箭头形式运行函数,因此return x = arr (\_ -> x)
.>>=
运行计算的bind的等价物然后使用输出作为函数的输入,f
然后可以计算下一个要运行的计算:首先m >>>
运行第一个计算m
,然后arr (\x -> (f x, ....
使用输出,应用函数f
,然后使用该箭头作为输入的app
行为就好像它是()
像往常一样作用于提供的输入的输出箭头.整齐!Tik*_*vis 10
观点A有点奇怪 - 一般来说,抽象不会比其他抽象更强大和更通用; 这两者是不一致的.拥有"更多权力"意味着更多地了解您正在使用的结构,这意味着更多的限制.在一个极端,您确切地知道您正在使用哪种类型.这非常强大; 你可以应用任何有效的功能.另一方面,它至少也不是通用的:用这个假设编写的代码只适用于那种类型!在另一个极端,您可以对您的类型一无所知(例如,有一个类型变量a
).这是非常通用的,适用于所有类型,但也没有强大,因为你没有足够的信息可以做任何事情!
更植根于实际代码的一个例子是之间的差值Functor
和Applicative
.在这里,Functor
更一般 - 严格来说,更多的类型是Functor
s而不是Applicative
s,因为每个类型都是Applicative
a Functor
但反之亦然.但是,由于Applicative
携带更多结构,它的功能更加强大.使用时Functor
,您只能在您的类型上映射单参数函数; 使用Applicative
,您可以映射任意数量的参数的函数.再一次:一个更通用,另一个更强大.
那是哪个呢?箭头比monad更强大还是更通用?这比比较函子,应用函子和monad更难,因为箭是一种非常不同的野兽.他们甚至有一种不同的类型:monads等人对* -> *
箭有所帮助* -> * -> *
.令人高兴的是,事实证明我们可以使用应用函子/ monad来识别箭头,因此我们实际上可以有意义地回答这个问题:箭头比monad更通用,因此效率更低.给定任何monad,我们可以构造一个箭头,但是我们不能为每个箭头构造一个monad.
基本思路如下:
instance Monad m => Category (a -> m b) where
id = return
(f . g) x = g x >>= f
instance Monad m => Arrow (a -> m b) where
arr f = return . f
first f (x, y) = f x >>= \ x' -> return (x', y)
Run Code Online (Sandbox Code Playgroud)
但是,因为我们有一个箭头例如a -> b
,我们必须包装a -> m b
成newtype
实际的代码.这newtype
称为Klesli
(因为Klesli类别).
然而,我们不能走另外一条道路-没有建设得到Monad
了的任何 Arrow
.发生这种情况是因为Arrow
计算不能根据流经它的值来改变其结构,而monad可以.解决这个问题的唯一方法是通过一些额外的原始函数为箭头抽象添加能量; 这正是做什么的ArrowApply
.
>>>
箭头的运算符是.
函数的概括,因此具有相同的一般限制.>>=
另一方面,更像是功能应用的概括.注意类型:因为>>>
,两边都是箭头; for >>=
,第一个参数是value(m a
),第二个参数是函数.而且,结果>>>
是另一个箭头,其结果>>=
是值.由于箭头只有>>>
但没有相同的概念>>=
,所以你不能将它们"应用"到一般的参数 - 你只能构造箭头管道.实际的应用/运行功能必须特定于任何给定的箭头.另一方面,Monads是>>=
根据默认情况下的一些应用概念定义的.
ArrowApply
只是扩展箭头app
,这是一个应用的一般概念.让我们想象一下正常的功能应用:
apply :: (b -> c) -> b -> c
apply f x = f x
Run Code Online (Sandbox Code Playgroud)
我们可以解决这个问题:
apply :: ((b -> c), b) -> c
Run Code Online (Sandbox Code Playgroud)
箭头概括函数的方法基本上是用->
变量(a
)替换.让我们apply
通过用中->
缀替换两次出现来做到这一点a
:
apply :: (b `a` c, b) `a` c
Run Code Online (Sandbox Code Playgroud)
我们仍然可以看到与第一个版本相同的结构apply
,只是没有结果而`a`
不是->
.现在,如果我们只是摆脱反引号并创建a
前缀,我们将获得以下签名app
:
app :: a (a b c, b) c
Run Code Online (Sandbox Code Playgroud)
所以我们看到如何ArrowApply
在箭头中添加一些应用概念.这是一个平行的>>=
,这是一个应用monad(或特别是形状的功能a -> m b
)的概念.这是从箭头构建monad的足够的额外结构,因此ArrowApply
是同构的Monad
.
我们为什么要用这些?老实说,我认为我们不会.箭头被高估了,所以坚持使用monad和applicative functor.
归档时间: |
|
查看次数: |
1662 次 |
最近记录: |