跳转到内容

Haskell/Arrows

来自维基教科书,开放的书籍,为开放的世界

箭头是单子的推广:每个单子都会产生一个箭头,但并非所有箭头都会产生单子。它们与单子有着相同的目的——为库提供通用结构——但更为通用。特别是,它们允许部分静态(独立于输入)或可能接受多个输入的计算概念。如果你的应用程序可以用单子很好地工作,你最好坚持使用它们。但如果你正在使用一个非常类似于单子但不是单子的结构,那么它可能就是一个箭头。

proc 和箭头尾部

[编辑 | 编辑源代码]

让我们首先了解箭头的表示法。我们将使用最简单的箭头(函数)并构建一些玩具程序,其目的仅仅是为了熟悉语法。

打开你的文本编辑器并创建一个 Haskell 文件,比如 toyArrows.hs

{-# LANGUAGE Arrows #-}

import Control.Arrow (returnA)

idA :: a -> a
idA = proc a -> returnA -< a

plusOne :: Int -> Int
plusOne = proc a -> returnA -< (a+1)

这些是我们前两个箭头。第一个是箭头形式的恒等函数,第二个稍微更有趣,是一个将输入加一的箭头。在 GHCi 中加载它,使用 -XArrows 扩展并查看会发生什么。

% ghci -XArrows toyArrows.hs   
   ___         ___ _
  / _ \ /\  /\/ __(_)
 / /_\// /_/ / /  | |      GHC Interactive, version 6.4.1, for Haskell 98.
/ /_\\/ __  / /___| |      http://www.haskell.org/ghc/
\____/\/ /_/\____/|_|      Type :? for help.

Loading package base-1.0 ... linking ... done.
Compiling Main             ( toyArrows.hs, interpreted )
Ok, modules loaded: Main.
*Main> idA 3
3
*Main> idA "foo"
"foo"
*Main> plusOne 3
4
*Main> plusOne 100
101

的确令人激动。到目前为止,我们已经在箭头表示法中看到了三个新的构造

  • 关键字 proc
  • -<
  • 导入的函数 returnA

既然我们已经知道如何将一个值加一,让我们尝试一个更困难的两倍的尝试:加二

 plusOne = proc a -> returnA -< (a+1)
 plusTwo = proc a -> plusOne -< (a+1)

一个简单的做法是将 (a+1) 作为输入提供给 plusOne 箭头。注意 plusOneplusTwo 之间的相似性。你应该注意到这里有一个基本模式,它有点类似于:proc FOO -> SOME_ARROW <- (SOMETHING_WITH_FOO)

练习
  1. plusOne 是一个箭头,因此根据上面的模式,returnA 也必须是一个箭头。你认为 returnA 做了什么?

do 标记

[编辑 | 编辑源代码]

我们当前的 plusTwo 实现实际上相当令人失望……它不应该只是 plusOne 两次吗?我们可以做得更好,但要做到这一点,我们需要引入 do 标记

 plusTwoBis = 
  proc a -> do b <- plusOne -< a
               plusOne -< b

现在在 GHCi 中试试这个

Prelude> :r
Compiling Main             ( toyArrows.hs, interpreted )
Ok, modules loaded: Main.
*Main> plusTwoBis 5
7

你可以使用这个 do 标记构建任意长的序列

plusFive =
 proc a -> do b <- plusOne -< a
              c <- plusOne -< b
              d <- plusOne -< c
              e <- plusOne -< d
              plusOne -< e

单子与箭头

[编辑 | 编辑源代码]
FIXME:我不确定,但我相信这里的意图是展示使用这个 proc 标记与仅仅使用一个常规的 do 链的区别


华夏公益教科书