箭头完全等同于应用函子?

时间:2014-07-10 04:06:52

标签: haskell applicative arrows category-theory

根据着名的论文Idioms are oblivious, arrows are meticulous, monads are promiscuous,箭头的表达能力(没有任何额外的类型类)应该在应用函子和monad之间严格地说:monad相当于ArrowApply和{{1}应该相当于纸张称为“静态箭头”的东西。但是,我不清楚这种“静态”意味着什么限制。

在讨论这三个类型类时,我能够在应用函子和箭头之间建立等价,我在下面的Applicative和{{1}之间众所周知的等价的上下文中提到了这一点。 }。这种结构是否正确? (在对此感到厌倦之前,我已经证明了大部分arrow laws)。这是否意味着MonadArrowApply完全相同?

Arrow

3 个答案:

答案 0 :(得分:29)

每个应用程序产生一个箭头,每个箭头产生一个应用程序,但它们不等同。如果您有一个箭头arr和一个态射arr a b,那么您就不会生成复制其功能的态射arr o (a \to b)。因此,如果您通过应用程序往返,您将失去一些功能。

申请人是monoidal仿函数。箭头也是类别,或类似地,在算子类别中的幺半群。这两个概念之间没有天然的联系。如果你原谅我的轻浮:在Hask中,事实证明箭头中的pro-functor的仿函数部分是一个幺正的仿函数,但是这种构造必然会忘记" pro"部分。

当你从箭头转到应用程序时,你忽略了一个箭头部分,它接受输入并只使用处理输出的部分。许多有趣的箭头以某种方式使用输入部分,因此通过将它们转换为应用程序,您放弃了有用的东西。

那就是说,在实践中,我发现应用更好的抽象来工作,而且几乎总是做我想要的。从理论上讲,箭头更强大,但我并没有在实践中发现自己使用箭头。

答案 1 :(得分:25)

让我们将IO applicative functor与IO monad的Kleisli箭头进行比较。

您可以使用箭头打印上一个箭头读取的值:

runKleisli ((Kleisli $ \() -> getLine) >>> Kleisli putStrLn) ()

但你不能用applicative functor做到这一点。使用applicative仿函数,所有效果都在之前将函数函数应用于仿函数中的参数。函数中的函数不能使用函数中的参数值来“调整”它自己的效果,可以这么说。

答案 2 :(得分:9)

(我已将以下内容发布到my blog并附带了扩展介绍)

Tom Ellis建议考虑一个涉及文件I / O的具体示例,所以让我们使用三个类型类比较它的三种方法。为简单起见,我们只关心两个操作:从文件中读取字符串并将字符串写入文件。文件将通过其文件路径标识:

type FilePath = String

Monadic 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)

另一方面,这意味着无法预先知道将由给定值action ∷ IOM α操纵的文件名集。通过“前期”,我的意思是能够编写纯函数fileNames :: IOM α → [FilePath]

当然,对于非基于IO的monad(例如我们有某种提取器函数的μ α → α),这种区别变得有点模糊,但考虑尝试仍然有意义在不评估monad影响的情况下提取信息(例如,我们可以问“我们对Reader Γ α有什么了解,而没有Γ类型的值?”。

我们无法在monad上真正进行静态分析的原因是因为绑定右侧的函数位于Haskell函数的空间中,因此完全不透明。

因此,让我们尝试将我们的界面限制为仅仅是一个应用程序的函子。

应用I / O

data IOF ∷ ⋆ → ⋆
instance Applicative IOF
readFile ∷ FilePath → IOF String
writeFile ∷ FilePath → String → IOF ()

由于IOF不是monad,因此无法编写readFilewriteFile,因此我们可以使用此接口来读取文件然后对其进行后处理内容纯粹,或写入文件;但是没有办法将文件的内容写入另一个文件。

如何更改writeFile的类型?

writeFile′ ∷ FilePath → IOF (String → ())

这个界面的主要问题是虽然它可以写出像

这样的东西
copy ∷ FilePath → FilePath → IOF ()
copy from to = writeFile′ to ⟨*⟩ readFile from

它会导致各种令人讨厌的问题,因为String → ()是一个将字符串写入文件的可怕模型,因为它会破坏参照透明度。例如,您希望运行此程序后out.txt的内容是什么?

(λ write → [write "foo", write "bar", write "foo"]) ⟨$⟩ writeFile′ "out.txt"

箭头化I / O的两种方法

首先,让我们得到两个基于箭头的I / O接口,而不是(事实上,不能)带来任何新的表:Kleisli IOM和{{1} }。

Applicarrow IOF的Kleisli箭头,模数为:

IOM

由于readFile ∷ Kleisli IOM FilePath String writeFile ∷ Kleisli IOM (FilePath, String) () 的输入仍然包含文件名和内容,我们仍然可以写writeFile(为简单起见,使用箭头符号)。请注意,copyIndirect ArrowApply实例的使用方式甚至没有使用过。

Kleisli IOM

copyIndirect ∷ Kleisli IOM (FilePath, FilePath) () copyIndirect = proc (index, target) → do from ← readFile ↢ index s ← readFile ↢ from writeFile ↢ (to, s) 的{​​{1}}将是:

Applicarrow

当然仍然存在无法撰写IOFreadFile ∷ FilePath → Applicarrow IOF () String writeFile ∷ FilePath → String → Applicarrow IOF () () 的相同问题。

正确的箭头化I / O接口

而不是将readFilewriteFile转换为箭头,如果我们从头开始,尝试在两者之间创建一些东西,就我们使用Haskell函数的位置和我们制作箭头的位置而言?采取以下界面:

IOM

由于IOF从箭头的输入端获取内容,我们仍然可以实现data IOA ∷ ⋆ → ⋆ → ⋆ instance Arrow IOA readFile ∷ FilePath → IOA () String writeFile ∷ FilePath → IOA String ()

writeFile

然而,copy的另一个参数是纯函数的,因此它不能依赖于例如copy ∷ FilePath → FilePath → IOA () () copy from to = readFile from >>> writeFile to 的输出。 writeFile;因此,使用箭头界面无法实现readFile

如果我们改变这个论点,这也意味着虽然我们事先无法知道最终会写入文件(在运行完整的copyIndirect管道本身之前),但我们可以静态地确定将被修改的文件名集。

结论

Monad对静态分析是不透明的,而应用函子在表达动态时间数据依赖性方面很差。事实证明,箭头可以在两者之间提供一个最佳点:通过仔细选择纯粹的功能和箭头化的输入,可以创建一个界面,允许动态行为的正确相互作用和静态分析的适应性。