函数式编程的核心思想之一是将算法表示为数据转换。在像 Haskell 这样的惰性语言中,我们甚至可以更进一步,将惰性数据结构视为具体计算。在非常真实的意义上,Haskell 的列表比普通的链表更像循环:它们可以增量计算,不必一次全部存在于内存中。同时,我们仍然获得了具有传递它并使用模式匹配检查它的能力的数据类型的许多优点。
考虑到这一点,用索引表示 for 循环的“技巧”是创建一个它可以采用的所有值的列表。您的示例可能是最简单的情况:从toi
获取所有值,因此我们可以使用 Haskell 的内置表示法来表示范围:0
255
[0..255]
在高层次上,这相当于 Haskell 的for (i = 0 to 255)
; 然后,我们可以通过递归函数或标准库中的高阶函数遍历这个列表来执行循环中的实际逻辑。(第二个选项是高度首选的。)
这种特殊的逻辑非常适合fold
. 折叠让我们逐项接收列表并建立某种结果。在每一步,我们都会得到一个列表项和到目前为止我们构建的结果的值。在这种特殊情况下,我们希望在递增索引的同时从左到右处理列表,因此我们可以使用foldl
; 一个棘手的部分是它将向后生成列表。
这是 的类型foldl
:
foldl :: (b -> a -> b) -> b -> [a] -> b
所以我们的函数接受我们的中间值和一个列表元素,并产生一个更新的中间值。由于我们正在构建一个列表并跟踪一个索引,我们的中间值将是一个包含两者的对。然后,一旦我们得到最终结果,我们可以忽略该idx
值并反转我们得到的最终列表:
a = let (result, _) = foldl step ([], 1) [0..255] in reverse result
where step (a, idx) i
| someCondition i = (idx:a, idx + 1)
| otherwise = (0:a, idx)
事实上,在跟踪一些中间状态(idx
在这种情况下)的同时转换列表的模式非常普遍,因此它在State
类型方面具有自己的功能。核心抽象涉及更多内容(通读 ["You could Have Invented Monads"][you] 以获得很好的介绍),但生成的代码实际上读起来非常愉快(我猜除了导入之外:P) :
import Control.Applicative
import Control.Monad
import Control.Monad.State
a = evalState (mapM step [0..255]) 1
where step i
| someCondition i = get <* modify (+ 1)
| otherwise = return 0
这个想法是我们在[0..255]
跟踪idx
后台的某些状态(的值)的同时进行映射。evalState
是我们如何将所有管道放在一起并获得最终结果。该step
函数应用于每个输入列表元素,还可以访问或修改状态。
该step
函数的第一种情况很有趣。<*
运算符告诉它先做左边的事情,然后做右边的事情,但返回左边的值。这让我们可以获取当前状态,增加它,但仍然返回我们在增加之前获得的值。我们的状态概念是一流的实体,我们可以拥有类似<*
的库函数这一事实非常强大——我发现这个特殊的习惯用法对于遍历树非常有用,并且其他类似的习惯用法对于其他代码也非常有用。