Functional programming 阅读时的几个问题;为什么函数式编程很重要;
我在读那篇著名的报纸,发现了一些我不懂的东西:Functional programming 阅读时的几个问题;为什么函数式编程很重要;,functional-programming,lazy-evaluation,termination,Functional Programming,Lazy Evaluation,Termination,我在读那篇著名的报纸,发现了一些我不懂的东西: 第8页底部: 与传统编程的最佳类比是可扩展语言——实际上,只要需要,编程语言可以使用新的控制结构进行扩展 我很好奇什么语言是“可扩展语言” 第9页中间,关于延迟求值和终止 g(f输入) 。。。作为额外的奖励,如果g在没有读取所有f的情况下终止 输出,则f被中止。程序f甚至可以是非终止程序, 产生有限数量的产出,因为它将被强制终止 一旦g完成。这允许将终止条件与 循环体-强大的模块化 我不明白为什么“g一结束f就会强制终止” 如果函数f定义为: 函数
f
定义为:
函数f(n)=1+f(n)
然后f(1)
将永远不会返回,直到堆栈溢出
在这种情况下,g
如何终止它?如果我完全误解了这些句子,很抱歉我没有太多的函数式语言经验让我们在哈斯克尔做这个
f :: Integer -> Integer
f n = 1 + f n
现在,为了应用给定的语句,我们需要一个函数g
,该函数在不需要完全计算其输入的情况下终止。对于整数,只有两种可能:要么完全计算,要么根本不计算。因此,唯一这样的实现是形式上的
g = const v
对于某些全局常数v
。现在,如果你尝试
前奏曲>让v=“我不变!”前奏曲>让g_v=v
前奏曲>g(f 1)
“我是不变的!”
序曲>g(F2)
“我是不变的!”
前奏曲>g(f 3)
“我是不变的!”
序曲>g(f 37)
“我是不变的!”
GHC甚至不开始评估
f
的结果来处理这个问题:它立即看到g
实际上并不需要它,并立即吐出结果。这是一个实现细节。然而,语言规范中固定的是求值是非严格的,这意味着正确地引用了您所引用的内容:求值不能无限期地挂起,试图完全获得f
的结果。因此,原则上,编译器也可以使f
首先计算一位(即循环),直到某个超时,决定检查是否已经完成足够的结果供g
使用。答案是肯定的(因为在g
中根本不需要任何东西),这将阻止f
的计算继续进行
当然,这并不是很有趣:常量函数很简单,充其量只与短路&&
操作符或类似的东西相关。但是,当您处理可以部分评估的数据结构时,问题变得更加令人兴奋。最简单的例子:Haskell列表。考虑
f' :: Integer -> [Integer]
f' n = [n ..]
这给出了所有数字的(无限)列表≥ 而不是n
。与f
类似,这显然不会以它的整个结果结束,但在本例中,现在不需要使用结果。比如说,
g' :: [Integer] -> Integer
g' (start:l) = sum $ takeWhile (< start^2) l
g'::[Integer]->Integer
g'(开始:l)=总计$takeWhile(
前奏曲>g'(f'1)0
前奏曲>g'(f'2)
3
前奏曲>g'(f'3)
30
序曲>g'(f'37)
935693
if
构造
(if true then else) ==> then
(if false then else) ==> else
我们可以构建一个cond
结构,其中每个body
仅当其与真值配对时才进行计算
(cond (test1 body1) (test2 body2) (test3 body3))
通过在运行时将其转换为一组嵌套的if语句
(do (if test1 body1 null)
(if test2 body2 null)
(if test3 body3 null))
请注意,这不能(通常)在运行时完成,因为在正常的Lisp求值中,我们必须先求值1-3中的每一个实体,然后才能确定它们是否应该执行,这使得cond
非常无用
在哈斯凯尔,我们从懒惰和纯洁中得到了很多好处。由于编译器在需要时才会计算,因此我们可以更明智地确定实际计算的时间。这通常被称为“使用数据结构作为控制结构”,但我们将在下文中看到更多内容
f = 3 -- pending!
它“挂起”直到需要它
print f -- now we compute `f` and print it
当您拥有复杂的数据结构时,这一点变得尤为重要,因为即使在计算其他部分时,这些部分也可以保持挂起状态。链表是一些很好的例子
-- [1,2,3,...] infinite list
nums = let go n = n : go (n+1)
in go 1
同样,请记住,在惰性设置中,此计算在实际需要时才会运行。如果我们把它打印出来,我们会看到数字一个接一个地流出来。在严格的设置下,解释器将被迫在打印单个列表之前计算整个列表。。。因此,它将挂起而不计算一个数字
作为另一个例子,考虑函数<代码>取,给定一个编号<代码> n>代码>,修剪列表中的第一个<代码> n>代码>元素。在有限列表上,它的行为是o
> take 3 [1,2,3,4,5,6]
[1,2,3]
> take 5 [1,2,3]
[1,2,3]
> take 5 nums
[1,2,3,4,5]
f n = 1 + f n
consumer fun = 3
> consumer f
3
data Peano = Zero | Successor Peano
0 ==> Zero
1 ==> Successor Zero
3 ==> Successor (Successor (Successor Zero))
f n = Successor (f n)
gte :: Peano -> Peano -> Bool
gte Zero Zero = True
gte Zero (Successor n) = True
gte (Successor n) (Successor m) = gte n m
gte Zero n ==> True -- even without evaluating `n` at all
gte (Successor Zero) n ==> True -- while evaluating only one "layer" of `n`
> gte (Successor (Successor Zero)) (f 10)
True