我试着学习箭的含义,但我不理解它们.
我使用了Wikibooks教程.我认为Wikibook的问题主要在于它似乎是为那些已经理解了这个主题的人写的.
有人可以解释箭头是什么以及我如何使用它们?
Joh*_*n L 72
我不知道一个教程,但我认为如果你看一些具体的例子,最容易理解箭头.我学习如何使用箭头的最大问题是没有任何教程或示例实际显示如何使用箭头,只是如何组成它们.所以,考虑到这一点,这是我的迷你教程.我将检查两个不同的箭头:函数和用户定义的箭头类型MyArr.
-- type representing a computation
data MyArr b c = MyArr (b -> (c,MyArr b c))
Run Code Online (Sandbox Code Playgroud)
1)箭头是从指定类型的输入到指定类型的输出的计算.箭头类型类有三种类型参数:箭头类型,输入类型和输出类型.查看箭头实例的实例头,我们发现:
instance Arrow (->) b c where
instance Arrow MyArr b c where
Run Code Online (Sandbox Code Playgroud)
箭头(无论是(->)或MyArr)是计算的抽象.
对于函数b -> c,b输入c是输出.
对于a MyArr b c,b是输入并且c是输出.
2)要实际运行箭头计算,请使用特定于箭头类型的函数.对于函数,您只需将函数应用于参数.对于其它箭头所示,需要有一个单独的函数(就像runIdentity,runState等,为单子).
-- 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 Code Online (Sandbox Code Playgroud)
3)箭头经常用于处理输入列表.对于函数,这些可以并行完成,但是对于某些箭头,任何给定步骤的输出都取决于先前的输入(例如,保持输入的总运行).
-- 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
Run Code Online (Sandbox Code Playgroud)
这是Arrows有用的一个原因.它们提供了一种计算模型,可以隐式地利用状态,而不会将该状态暴露给程序员.程序员可以使用箭头化计算并将它们组合在一起以创建复杂的系统.
这是一个MyArr,它记录了它收到的输入数量:
-- count the number of inputs received:
count :: MyArr b Int
count = count' 0
where
count' n = MyArr (\_ -> (n+1, count' (n+1)))
Run Code Online (Sandbox Code Playgroud)
现在该函数runMyArrList count将列表长度n作为输入,并返回从1到n的Int列表.
请注意,我们仍然没有使用任何"箭头"函数,即Arrow类方法或根据它们编写的函数.
4)上面的大多数代码都是特定于每个Arrow实例[1].在一切Control.Arrow(和Control.Category)约为构成箭头作出新的箭头.如果我们假装Category是Arrow的一部分而不是单独的类:
-- 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
Run Code Online (Sandbox Code Playgroud)
该>>>函数采用两个箭头,并使用第一个输出作为第二个输入.
这是另一个操作员,通常称为"扇出":
-- &&& 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.
Run Code Online (Sandbox Code Playgroud)
由于Control.Arrow提供了一种组合计算的方法,这里有一个例子:
-- function that, given an input n, returns "n+1" and "n*2"
calc1 :: Int -> (Int,Int)
calc1 = (+1) &&& (*2)
Run Code Online (Sandbox Code Playgroud)
我经常发现calc1在复杂的折叠中有用的函数,或者在指针上运行的函数.
该Monad类型的类为我们提供了一元计算,合并成使用一个新的单子计算手段>>=的功能.类似地,Arrow类为我们提供了手段arrowized计算合并到单个新arrowized计算使用一些基本函数(first,arr,和***,具有>>>与id从Control.Category).也像Monads一样,问题是"箭头做什么?" 不能普遍回答.这取决于箭头.
不幸的是,我不知道野外箭头实例的许多例子.功能和FRP似乎是最常见的应用程序.HXT是唯一可以想到的其他重要用法.
[1]除外count.可以编写一个count函数,对任何实例执行相同的操作ArrowLoop.
C. *_*ann 34
从你的历史记录堆栈溢出一眼,我会假设你舒服一些其他的标准类型的类,特别是Functor和Monoid,并与那些短暂的比喻开始.
单个操作Functor是fmap,作为map列表的通用版本.这几乎是类型类的全部目的; 它定义了"你可以映射的东西".因此,从某种意义上说,Functor代表了列表特定方面的概括.
操作Monoid是空列表的通用版本(++),并且它定义了"可以关联地组合的事物,以及具有身份值的特定事物".列表几乎是符合该描述的最简单的事物,并且Monoid代表了列表的这一方面的概括.
以相同的方式如上述两个,在所述操作Category类型类中广义的版本id和(.),它定义"在特定方向上连接两个类型的东西,可以连接头-尾".因此,这代表了功能的这一方面的概括.值得注意的是,概括不包括currying或函数应用.
的Arrow类型的类建立关的Category,但基本概念是相同的:Arrows为东西组成类似功能,并具有一个"同一性箭头"为任何类型的定义.在Arrow类本身上定义的附加操作只是定义了一种将任意函数提升到一种Arrow方法,以及将两个"并行"箭头组合为元组之间的单箭头的方法.
因此,首先要记住的是构建Arrows的表达式本质上是精心设计的函数组合.组合器喜欢(***)并且(>>>)用于编写"无点"样式,而proc符号提供了一种在连接时为输入和输出分配临时名称的方法.
这里要注意的一件有用的事情是,即使Arrows有时被描述为s的"下一步" Monad,但那里的关系确实不是非常有意义.对于任何Monad你可以使用Kleisli箭头,它只是类似的类型的函数a -> m b.的(<=<)在操作者Control.Monad为这些箭头组合物.另一方面,除非你也包括这门课,否则你Arrow不会得到你.所以没有直接联系.MonadArrowApply
这里的关键区别在于,Monads可以用于对计算进行排序并逐步进行,Arrows在某种意义上就像常规函数一样是"永恒的".它们可以包括拼接的额外机械和功能(.),但更像是构建管道而不是累积动作.
其他相关类型类为箭头添加了附加功能,例如能够组合箭头Either以及(,).
我最喜欢的一个例子Arrow是状态流传感器,它看起来像这样:
data StreamTrans a b = StreamTrans (a -> (b, StreamTrans a b))
Run Code Online (Sandbox Code Playgroud)
甲StreamTrans箭头转换的输入值的输出和的本身就是一个"已更新"版本; 考虑这与有状态的不同之处Monad.
Arrow为上述类型编写实例及其相关类型类可能是理解它们如何工作的一个很好的练习!
我之前也写了一个类似的答案,你可能会觉得有帮助.
小智 30
我想补充一点,Haskell中的箭头比基于文献的箭头要简单得多.它们只是函数的抽象.
要了解这实际上是如何有用的,请考虑您有一堆想要编写的函数,其中一些是纯函数,一些是monadic.例如,f :: a -> b和g :: b -> m1 c,和h :: c -> m2 d.
知道所涉及的每种类型,我可以手工构建合成,但合成的输出类型必须反映中间单子类型(在上面的例子中m1 (m2 d)).如果我只是想,如果他们只是治疗的功能a -> b,b -> c和c -> d?也就是说,我想抽象出monad的存在,并仅仅推断出基础类型.我可以用箭头做到这一点.
这是一个箭头,它为IO monad中的函数抽象出IO的存在,这样我就可以用纯函数组合它们,而组合代码不需要知道涉及IO.我们首先定义一个IOArrow来包装IO函数:
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)
Run Code Online (Sandbox Code Playgroud)
然后我做了一些我想要编写的简单函数:
foo :: Int -> String
foo = show
bar :: String -> IO Int
bar = return . read
Run Code Online (Sandbox Code Playgroud)
并使用它们:
main :: IO ()
main = do
let f = arr (++ "!") . arr foo . IOArrow bar . arr id
result <- runIOArrow f "123"
putStrLn result
Run Code Online (Sandbox Code Playgroud)
在这里我调用IOArrow和runIOArrow,但是如果我在多态函数库中传递这些箭头,他们只需要接受"Arrow a => ab c"类型的参数.没有任何库代码需要知道涉及monad.只有箭头的创建者和最终用户需要知道.
推广IOArrow以在任何Monad中使用函数称为"Kleisli箭头",并且已经有一个内置箭头用于执行此操作:
main :: IO ()
main = do
let g = arr (++ "!") . arr foo . Kleisli bar . arr id
result <- runKleisli g "123"
putStrLn result
Run Code Online (Sandbox Code Playgroud)
你当然也可以使用箭头组合运算符和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
Run Code Online (Sandbox Code Playgroud)
这里应该很清楚,虽然main知道IO monad涉及,
arrowUser但不是.没有方法可以在arrowUser
没有箭头的情况下"隐藏"IO - 而不是不依靠unsafePerformIO将中间monadic值转换回纯粹的值(从而永远丢失该上下文).例如:
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
Run Code Online (Sandbox Code Playgroud)
尝试编写没有unsafePerformIO,也不必arrowUser'处理任何Monad类型的参数.