Haskell 箭头与应用函子完全等价?
根据这篇著名的论文,arrow(没有任何额外的类型类)的表达能力应该严格介于applicative functor和monad之间:monad相当于Haskell 箭头与应用函子完全等价?,haskell,applicative,arrows,category-theory,Haskell,Applicative,Arrows,Category Theory,根据这篇著名的论文,arrow(没有任何额外的类型类)的表达能力应该严格介于applicative functor和monad之间:monad相当于ArrowApply,而applicative应该相当于论文称之为“静态箭头”的东西。然而,我不清楚这种“静态”的限制意味着什么 在讨论这三个类型类时,我能够在应用函子和箭头之间建立一个等价关系,我在下面介绍Monad和ArrowApply之间众所周知的等价关系。这个结构正确吗?(在厌倦之前,我已经证明了其中的大部分)。这难道不意味着箭头和应用程序完
ArrowApply
,而applicative
应该相当于论文称之为“静态箭头”的东西。然而,我不清楚这种“静态”的限制意味着什么
在讨论这三个类型类时,我能够在应用函子和箭头之间建立一个等价关系,我在下面介绍Monad
和ArrowApply
之间众所周知的等价关系。这个结构正确吗?(在厌倦之前,我已经证明了其中的大部分)。这难道不意味着箭头
和应用程序
完全相同吗
{-# LANGUAGE TupleSections, NoImplicitPrelude #-}
import Prelude (($), const, uncurry)
-- In the red corner, we have arrows, from the land of * -> * -> *
import Control.Category
import Control.Arrow hiding (Kleisli)
-- In the blue corner, we have applicative functors and monads,
-- the pride of * -> *
import Control.Applicative
import Control.Monad
-- Recall the well-known result that every monad yields an ArrowApply:
newtype Kleisli m a b = Kleisli{ runKleisli :: a -> m b}
instance (Monad m) => Category (Kleisli m) where
id = Kleisli return
Kleisli g . Kleisli f = Kleisli $ g <=< f
instance (Monad m) => Arrow (Kleisli m) where
arr = Kleisli . (return .)
first (Kleisli f) = Kleisli $ \(x, y) -> liftM (,y) (f x)
instance (Monad m) => ArrowApply (Kleisli m) where
app = Kleisli $ \(Kleisli f, x) -> f x
-- Every arrow arr can be turned into an applicative functor
-- for any choice of origin o
newtype Arrplicative arr o a = Arrplicative{ runArrplicative :: arr o a }
instance (Arrow arr) => Functor (Arrplicative arr o) where
fmap f = Arrplicative . (arr f .) . runArrplicative
instance (Arrow arr) => Applicative (Arrplicative arr o) where
pure = Arrplicative . arr . const
Arrplicative af <*> Arrplicative ax = Arrplicative $
arr (uncurry ($)) . (af &&& ax)
-- Arrplicatives over ArrowApply are monads, even
instance (ArrowApply arr) => Monad (Arrplicative arr o) where
return = pure
Arrplicative ax >>= f =
Arrplicative $ (ax >>> arr (runArrplicative . f)) &&& id >>> app
-- Every applicative functor f can be turned into an arrow??
newtype Applicarrow f a b = Applicarrow{ runApplicarrow :: f (a -> b) }
instance (Applicative f) => Category (Applicarrow f) where
id = Applicarrow $ pure id
Applicarrow g . Applicarrow f = Applicarrow $ (.) <$> g <*> f
instance (Applicative f) => Arrow (Applicarrow f) where
arr = Applicarrow . pure
first (Applicarrow f) = Applicarrow $ first <$> f
{-#语言元组,NoImplicitPrelude}
导入前奏曲(($),常量,未终止)
--在红色的角落里,我们有箭头,来自*->*->*
进口管制.类别
导入控制.箭头隐藏(Kleisli)
--在蓝色的角落,我们有应用函子和单子,
--骄傲的*->*
导入控制
进口管制
--回想一下众所周知的结果,即每个单子都会产生一个箭头Apply:
newtype Kleisli m a b=Kleisli{runKleisli::a->m b}
实例(Monad m)=>类别(Kleisli m),其中
id=Kleisli返回
克莱斯利g。Kleisli f=Kleisli$g箭头(Kleisli m)其中
arr=Kleisli。(返回。)
第一(Kleisli f)=Kleisli$\(x,y)->liftM(,y)(f x)
实例(Monad m)=>箭头应用(Kleisli m),其中
app=Kleisli$\(Kleisli f,x)->f x
--每个arrow arr都可以转化为一个应用函子
--对于任何来源的选择
新类型Arrplicative arr o a=Arrplicative{runArrplicative::arr o a}
实例(Arrow arr)=>函子(arreplicative arr o),其中
fmap f=复制的。(阿里夫)。运行复制
实例(箭头arr)=>应用程序(应用程序arr o),其中
纯=复制的。啊。常数
arreplicative af arreplicative ax=arreplicative$
arr(未到期($)。(af&ax)
--箭头上的应用程序甚至是单子
实例(箭头应用arr)=>Monad(arreplicative arr o),其中
返回=纯
A复制ax>>=f=
Arrplicative$(ax>>>arr(runArrplicative.f))&&id>>应用程序
--每个应用函子f都可以变成箭头??
newtype applicatorrow f a b=applicatorrow{runapplicatorrow::f(a->b)}
实例(应用程序f)=>类别(应用程序f),其中
id=应用程序箭头$pure id
应用箭头g。应用程序箭头f=应用程序箭头$(.)g f
实例(应用程序f)=>箭头(应用程序箭头f),其中
arr=应用箭头。纯净的
第一个(应用程序箭头f)=应用程序箭头$first f
每个应用程序都会生成一个箭头,每个箭头都会生成一个应用程序,但它们并不等价。如果您有一个箭头arr
和一个态射arrab
,那么并不意味着您可以生成一个复制其功能的态射arro(a\to b)
。因此,如果您通过applicative来回访问,就会丢失一些特性
应用程序是幺半函子。箭头是profunctor,也是profunctor范畴中的范畴,或者等价地,是profunctor范畴中的幺半群。这两个概念之间没有天然的联系。请原谅我的轻率:在Hask中,箭头中的前函子的函子部分是一个幺半函子,但这种构造必然会忘记“前”部分
从箭头转到应用程序时,忽略了箭头中接受输入的部分,而只使用了处理输出的部分。许多有趣的箭头以这样或那样的方式使用输入部分,因此通过将它们转换为应用程序,您就放弃了有用的东西
这就是说,在实践中,我发现应用程序是一种更好的抽象,而且它几乎总是做我想做的事情。从理论上讲,箭的威力更大,但我发现自己并没有在实践中使用它们 让我们将IO应用函子与IO单子的Kleisli箭头进行比较 您可以使用一个箭头打印上一个箭头读取的值:
runKleisli ((Kleisli $ \() -> getLine) >>> Kleisli putStrLn) ()
但是你不能用应用函子。对于应用函子,所有效果都发生在将函子中的函数应用于函子中的参数之前。可以说,函子中的函数不能使用函子中参数内的值来“调节”其自身的效果。(我已将下面的内容发布到,并提供了扩展的介绍)
Tom Ellis建议考虑一个涉及文件I/O的具体示例,因此让我们比较使用三个类型类的三种方法。为了简单起见,我们只关心两个操作:从文件中读取字符串和将字符串写入文件。文件将通过其文件路径进行标识:
type FilePath = String
一元I/O
我们的第一个I/O接口定义如下:
data IOM ∷ ⋆ → ⋆
instance Monad IOM
readFile ∷ FilePath → IOM String
writeFile ∷ FilePath → String → IOM ()
使用此接口,我们可以将文件从一个路径复制到另一个路径:
copy ∷ FilePath → FilePath → IOM ()
copy from to = readFile from >>= writeFile to
然而,我们可以做的远不止这些:我们操作的文件的选择可以取决于上游的效果。例如,下面的函数获取包含文件名的索引文件,并将其复制到给定的目标目录:
copyIndirect ∷ FilePath → FilePath → IOM ()
copyIndirect index target = do
from ← readFile index
copy from (target ⟨/⟩ to)
另一方面,这意味着无法预先知道将由给定值操作操作的文件名集∷ IOMα
。我所说的“前期”是指编写纯函数fileNames::IOMα的能力→ [文件路径]
当然,对于非基于IO的单子(例如我们有某种提取器函数μα的单子)→ α
),这种区别变得有点模糊,但考虑在不评估单子效果的情况下提取信息仍然有意义(例如,我们可以问“我们对了解多少?”
data IOF ∷ ⋆ → ⋆
instance Applicative IOF
readFile ∷ FilePath → IOF String
writeFile ∷ FilePath → String → IOF ()
writeFile′ ∷ FilePath → IOF (String → ())
copy ∷ FilePath → FilePath → IOF ()
copy from to = writeFile′ to ⟨*⟩ readFile from
(λ write → [write "foo", write "bar", write "foo"]) ⟨$⟩ writeFile′ "out.txt"
readFile ∷ Kleisli IOM FilePath String
writeFile ∷ Kleisli IOM (FilePath, String) ()
copyIndirect ∷ Kleisli IOM (FilePath, FilePath) ()
copyIndirect = proc (index, target) → do
from ← readFile ↢ index
s ← readFile ↢ from
writeFile ↢ (to, s)
readFile ∷ FilePath → Applicarrow IOF () String
writeFile ∷ FilePath → String → Applicarrow IOF () ()
data IOA ∷ ⋆ → ⋆ → ⋆
instance Arrow IOA
readFile ∷ FilePath → IOA () String
writeFile ∷ FilePath → IOA String ()
copy ∷ FilePath → FilePath → IOA () ()
copy from to = readFile from >>> writeFile to