In the same way as the above two, the operations on the Category type class are generalized versions of id and (.), and it defines "things connecting two types in a particular direction, that can be connected head-to-tail". So, this represents a generalization of that aspect of 功能. Notably not included in the generalization are currying or function application.
The Arrow type class builds off of Category, but the underlying concept is the same: Arrows are things that compose like functions and have an "identity arrow" defined for any type. The additional operations defined on the Arrow class itself just define a way to lift an arbitrary function to an Arrow and a way to combine two arrows "in parallel" as a single arrow between tuples.
这里需要注意的一点是,尽管 Arrow有时被描述为 Monad的“下一步”,但它们之间并没有什么特别有意义的关系。对于任何 Monad,您都可以使用 Kleisli 箭头,它只是具有类似于 a -> m b的类型的函数。Control.Monad中的 (<=<)操作符是这些操作符的箭头组合。另一方面,除非你也包括 ArrowApply类,否则 Arrow不会给你一个 Monad。所以没有直接联系。
The key difference here is that whereas Monads can be used to sequence computations and do things step-by-step, Arrows are in some sense "timeless" just like regular functions. They can include extra machinery and functionality that gets spliced in by (.), but it's more like building a pipeline, not accumulating actions.
The other related type classes add additional functionality to an arrow, such as being able to combine arrows with Either as well as (,).
我最喜欢的 Arrow的例子是 有状态流传感器有状态流传感器,它看起来像这样:
data StreamTrans a b = StreamTrans (a -> (b, StreamTrans a b))
I don't know a tutorial, but I think it's easiest to understand arrows if you look at some concrete examples. The biggest problem I had learning how to use arrows was that none of the tutorials or examples actually show how to 使用 arrows, just how to compose them. So, with that in mind, here's my mini-tutorial. I'll examine two different arrows: functions and a user-defined arrow type MyArr.
-- type representing a computation
data MyArr b c = MyArr (b -> (c,MyArr b c))
1) An Arrow is a calculation from input of a specified type to output of a specified type. The arrow typeclass takes three type arguments: the arrow type, the input type, and the output type. Looking at the instance head for arrow instances we find:
instance Arrow (->) b c where
instance Arrow MyArr b c where
箭头((->)或 MyArr)是计算的抽象。
对于函数 b -> c,b是输入,c是输出。
For a MyArr b c, b is the input and c is the output.
-- run a function arrow
runF :: (b -> c) -> b -> c
runF = id
-- run a MyArr arrow, discarding the remaining computation
runMyArr :: MyArr b c -> b -> c
runMyArr (MyArr step) = fst . step
-- run a function arrow over multiple inputs
runFList :: (b -> c) -> [b] -> [c]
runFList f = map f
-- run a MyArr over multiple inputs.
-- Each step of the computation gives the next step to use
runMyArrList :: MyArr b c -> [b] -> [c]
runMyArrList _ [] = []
runMyArrList (MyArr step) (b:bs) = let (this, step') = step b
in this : runMyArrList step' bs
-- combine two arrows in sequence
>>> :: Arrow a => a b c -> a c d -> a b d
-- the function arrow instance
-- >>> :: (b -> c) -> (c -> d) -> (b -> d)
-- this is just flip (.)
-- MyArr instance
-- >>> :: MyArr b c -> MyArr c d -> MyArr b d
>>>函数接受两个箭头,并将第一个箭头的输出作为第二个箭头的输入。
Here's another operator, commonly called "fanout":
-- &&& applies two arrows to a single input in parallel
&&& :: Arrow a => a b c -> a b c' -> a b (c,c')
-- function instance type
-- &&& :: (b -> c) -> (b -> c') -> (b -> (c,c'))
-- MyArr instance type
-- &&& :: MyArr b c -> MyArr b c' -> MyArr b (c,c')
-- first and second omitted for brevity, see the accepted answer from KennyTM's link
-- for further details.
由于 Control.Arrow提供了一种组合计算的方法,这里有一个例子:
-- function that, given an input n, returns "n+1" and "n*2"
calc1 :: Int -> (Int,Int)
calc1 = (+1) &&& (*2)
我经常发现像 calc1这样的函数在复杂的折叠中很有用,或者像操作指针这样的函数。
Monad类型类为我们提供了一种使用 >>=函数将一元计算合并为一个新的一元计算的方法。类似地,Arrow类为我们提供了使用几个基本函数(first,arr和 ***,以及 Control 的 >>>和 id)将箭头化计算组合成一个新的箭头化计算的方法。类别)。与 Monads 类似的问题还有“箭是干什么的?”不能一概而论。这取决于箭头。
为了了解这是如何实际有用的,请考虑您有一些
functions you want to compose, where some of them are pure and some are
例如,f :: a -> b、 g :: b -> m1 c和 h :: c -> m2 d。
了解所涉及的每种类型,我可以手工构建一个作文,但
该组合物的输出类型必须反映中间体
单子类型(在上面的例子中,m1 (m2 d))。如果我只是想治疗
就像它们只是 a -> b,b -> c,和 c -> d一样? 就是说,
我想抽象出单子的存在和理由,只有关于
underlying types. I can use arrows to do exactly this.
data IOArrow a b = IOArrow { runIOArrow :: a -> IO b }
instance Category IOArrow where
id = IOArrow return
IOArrow f . IOArrow g = IOArrow $ f <=< g
instance Arrow IOArrow where
arr f = IOArrow $ return . f
first (IOArrow f) = IOArrow $ \(a, c) -> do
x <- f a
return (x, c)
然后我创建一些简单的函数:
foo :: Int -> String
foo = show
bar :: String -> IO Int
bar = return . read
使用它们:
main :: IO ()
main = do
let f = arr (++ "!") . arr foo . IOArrow bar . arr id
result <- runIOArrow f "123"
putStrLn result
这里我调用 IOArrow 和 runIOArrow,但是如果我传递这些箭头
在一个多态函数库中,他们只需要接受
类型为“ Arrow a = > a b c”的参数
只有单子的创造者和最终用户才能意识到单子的存在
Arrow 需要知道。
main :: IO ()
main = do
let g = arr (++ "!") . arr foo . Kleisli bar . arr id
result <- runKleisli g "123"
putStrLn result
当然,您也可以使用箭头组合运算符和 proc 语法来
让我们更清楚地了解一下箭头的含义:
arrowUser :: Arrow a => a String String -> a String String
arrowUser f = proc x -> do
y <- f -< x
returnA -< y
main :: IO ()
main = do
let h = arr (++ "!")
<<< arr foo
<<< Kleisli bar
<<< arr id
result <- runKleisli (arrowUser h) "123"
putStrLn result
arrowUser' :: (String -> String) -> String -> String
arrowUser' f x = f x
main' :: IO ()
main' = do
let h = (++ "!") . foo . unsafePerformIO . bar . id
result = arrowUser' h "123"
putStrLn result
因此,本质上,对于某个值 x,首先调用一个函数,我们假设它可能返回 null(function 1) ,另一个函数可能返回 null,或者可以互换地被分配给 null,最后,第三个函数也可能返回 null。现在给定值 x,将 x 传递给 function 3,只有在这样的情况下,如果它不返回 null,将这个值传递给 function 2,并且只有当这个值不为 null 时,才将这个值传递给 function 1。它更具有确定性,并且控制流允许您构造更复杂的异常处理。