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定义为: 函数

我在读那篇著名的报纸,发现了一些我不懂的东西:

  • 第8页底部:

    与传统编程的最佳类比是可扩展语言——实际上,只要需要,编程语言可以使用新的控制结构进行扩展

    我很好奇什么语言是“可扩展语言”

  • 第9页中间,关于延迟求值和终止

    g(f输入)

    。。。作为额外的奖励,如果g在没有读取所有f的情况下终止 输出,则f被中止。程序f甚至可以是非终止程序, 产生有限数量的产出,因为它将被强制终止 一旦g完成。这允许将终止条件与 循环体-强大的模块化

    我不明白为什么“g一结束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

  • 可扩展语言
  • 可扩展语言最常见的例子可能是Lisp系列(common Lisp、Scheme、Clojure),它有一个语法宏系统。这个宏系统允许您将Lisp程序视为在两个时间点上运行的程序,一次是“在编译期间”,另一次是“在运行时”。“编译期间”阶段接受Lisp程序的某些部分,并从语法上将其扩展为更复杂的Lisp程序。“运行时”部分的运行方式与常规语言类似

    这种阶段性的区别允许我们扩展语言的控制结构,因为我们可以在不执行任何操作的情况下抽象出常见的习语。例如,假设我们有一个
    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