首页 文章

真正理解程序和功能之间的区别

提问于
浏览
106

我真的很难理解程序和函数编程范例之间的区别 .

以下是维基百科关于函数式编程的前两段:

在计算机科学中,函数式编程是一种编程范式,它将计算视为数学函数的评估,并避免状态和可变数据 . 它强调功能的应用,与强调状态变化的命令式编程风格形成对比 . 函数式编程的根源在于lambda演算,这是一种在20世纪30年代开发的用于研究函数定义,函数应用和递归的正式系统 . 许多函数式编程语言可以被视为lambda演算的详细说明 . 在实践中,数学函数与命令式编程中使用的“函数”概念之间的区别在于命令式函数可能具有副作用,从而改变程序状态的值 . 因此,它们缺乏参考透明性,即,相同的语言表达式可以根据执行程序的状态在不同时间导致不同的值 . 相反,在函数代码中,函数的输出值仅取决于输入到函数的参数,因此使用参数x的相同值调用函数f两次将产生相同的结果f(x) . 消除副作用可以更容易理解和预测程序的行为,这是开发函数式编程的关键动机之一 .

在第2段中,它说

相反,在函数代码中,函数的输出值仅取决于输入到函数的参数,因此使用相同的参数x调用函数f两次将产生相同的结果f(x)两次 .

程序编程的情况不一样吗?

什么应该在程序性和功能性中脱颖而出?

8 回答

  • 266

    Functional Programming

    函数式编程是指将函数视为值的能力 .

    让我们考虑一下"regular"值的类比 . 我们可以使用两个整数值并使用 + 运算符将它们组合以获得新的整数 . 或者我们可以将整数乘以浮点数来获得浮点数 .

    在函数式编程中,我们可以使用composelift等运算符组合两个函数值来生成新的函数值 . 或者我们可以使用mapfold等运算符组合函数值和数据值来生成新的数据值 .

    请注意,许多语言都具有函数式编程功能 - 甚至通常不被认为是函数式语言的语言 . 即使祖父FORTRAN支持功能值,尽管它没有提供功能组合运算符的方式 . 对于一种被称为“功能”的语言,它需要以一种很大的方式包含函数式编程功能 .

    Procedural Programming

    程序编程是指将一个共同的指令序列封装到一个过程中的能力,这样就可以从许多地方调用这些指令而无需借助复制和粘贴 . 由于程序是编程的早期发展,因此该功能几乎总是与机器或汇编语言编程所需的编程风格相关联:强调存储位置概念的样式和在这些位置之间移动数据的指令 .

    Contrast

    这两种风格并不是完全相反的 - 它们彼此不同 . 有些语言完全包含这两种风格(例如LISP) . 以下场景可能会给出两种样式的一些差异 . 让我们为无意义的要求编写一些代码,我们想要确定列表中的所有单词是否都有奇数个字符 . 一,程序风格:

    function allOdd(words) {
      var result = true;
      for (var i = 0; i < length(words); ++i) {
        var len = length(words[i]);
        if (!odd(len)) {
          result = false;
          break;
        }
      }
      return result;
    }
    

    我会认为这个例子是可以理解的 . 现在,功能风格:

    function allOdd(words) {
      return apply(and, map(compose(odd, length), words));
    }
    

    从内到外,这个定义做了以下事情:

    • compose(odd, length) 结合了 oddlength 函数来生成一个新函数,用于确定字符串的长度是否为奇数 .

    • map(..., words)words 中的每个元素调用新函数,最终返回一个新的布尔值列表,每个都指示是否相应的单词有奇数个字符 .

    • apply(and, ...) 将"and"运算符应用于结果列表,并将所有布尔值组合在一起以产生最终结果 .

    从这些示例中可以看出,过程式编程非常关注在变量中移动值并明确描述生成最终结果所需的操作 . 相反,功能样式强调将初始输入转换为最终输出所需的功能组合 .

    该示例还显示了过程代码与功能代码的典型相对大小 . 此外,它表明程序代码的性能特征可能比功能代码更容易看到 . 考虑一下:这些函数是否计算了列表中所有单词的长度,或者每个单词在找到第一个偶数长度单词后是否立即停止?另一方面,功能代码允许高质量的实现执行一些非常严重的优化,因为它主要表达意图而不是显式算法 .

    Further Reading

    这个问题出现了很多......例如,见:

    John Backus的图灵奖讲座详细阐述了函数式编程的动机:

    Can Programming Be Liberated from the von Neumann Style?

    我真的不应该在目前的背景下提到这篇论文,因为它很快就会变得非常技术性 . 我无法抗拒,因为我认为这是真正的基础 .


    Addendum - 2013

    评论人士指出,流行的当代语言提供了除程序和功能之外的其他编程风格 . 这些语言通常提供以下一种或多种编程风格:

    • 查询(例如列表推导,语言集成查询)

    • dataflow(例如隐式迭代,批量操作)

    • 面向对象(例如封装的数据和方法)

    • 面向语言(例如特定于应用程序的语法,宏)

    请参阅下面的注释,以获取有关此响应中的伪代码示例如何从其他样式中提供的某些工具中受益的示例 . 特别是,程序示例将受益于几乎任何更高级别构造的应用 .

    所展示的示例有意避免混合使用这些其他编程风格,以强调所讨论的两种风格之间的区别 .

  • 1

    功能和命令式编程之间的真正区别在于思维模式 - 命令式程序员正在考虑变量和内存块,而功能程序员正在思考,“我怎样才能将输入数据输入到我的输出数据" - your "程序中”是管道和集合转换数据以将其从输入转换为输出 . 这是IMO的有趣部分,而不是"Thou shalt not use variables"位 .

    作为这种心态的结果,FP程序通常描述会发生什么,而不是它将如何发生的具体机制 - 这是强大的,因为如果我们能够清楚地说明"Select"和"Where"和"Aggregate"的含义,我们可以自由地换掉他们的实现,就像我们使用AsParallel()一样,突然我们的单线程应用程序扩展到n个核心 .

  • 6
    Isn't that the same exact case for procedural programming?
    

    不,因为程序代码可能有副作用 . 例如,它可以在调用之间存储状态 .

    也就是说,可以在被认为是程序性的语言中编写满足此约束的代码 . 并且还可以编写在某些被认为是功能性的语言中打破此约束的代码 .

  • 2

    我不同意WReach的回答 . 让我们解释他的答案,看看分歧来自何处 .

    首先,他的代码:

    function allOdd(words) {
      var result = true;
      for (var i = 0; i < length(words); ++i) {
        var len = length(words[i]);
        if (!odd(len)) {
          result = false;
          break;
        }
      }
      return result;
    }
    

    function allOdd(words) {
      return apply(and, map(compose(odd, length), words));
    }
    

    首先要注意的是他正在混淆:

    • 功能

    • 面向表达和

    • 迭代器中心

    编程,并且缺乏迭代样式编程的能力,使其具有比典型功能样式更明确的控制流程 .

    让我们快速谈谈这些 .

    以表达为中心的风格是事物尽可能地评估事物的风格 . 虽然函数式语言因其对表达式的热爱而闻名,但它只能在没有表达式的情况下完成,而只是语句 .

    lengths: map words length
    each_odd: map lengths odd
    all_odd: reduce each_odd and
    

    这几乎与之前给出的相同,除了函数纯粹通过语句和绑定链链接 .

    迭代器中心编程风格可能是一种蟒蛇 . 让我们使用纯粹的迭代,迭代器为中心的风格:

    def all_odd(words):
        lengths = (len(word) for word in words)
        each_odd = (odd(length) for length in lengths)
        return all(each_odd)
    

    这不起作用,因为每个子句都是一个迭代过程,它们通过显式暂停和重新开始堆栈帧绑定在一起 . 语法可以部分地来自功能语言,但是它应用于它的完全迭代的实施例 .

    当然,你可以压缩这个:

    def all_odd(words):
        return all(odd(len(word)) for word in words)
    

    现在势在必行看起来不那么糟糕,是吗? :)

    最后一点是关于更明确的控制流程 . 让我们重写原始代码来使用它:

    function allOdd(words) {
        for (var i = 0; i < length(words); ++i) {
            if (!odd(length(words[i]))) {
                return false;
            }
        }
        return true;
    }
    

    使用迭代器,您可以:

    function allOdd(words) {
        for (word : words) { if (!odd(length(word))) { return false; } }
        return true;
    }
    

    那么,如果区别在于函数式语言的重点是什么:

    return all(odd(len(word)) for word in words)
    
    return apply(and, map(compose(odd, length), words))
    
    for (word : words) { if (!odd(length(word))) { return false; } }
    return true;
    

    函数式编程语言的主要决定性特征是它将突变作为典型编程模型的一部分 . 人们经常认为这意味着函数式编程语言没有语句或使用表达式,但这些都是简化 . 功能语言用行为声明替换显式计算,然后语言执行减少 .

    限制自己使用这个功能子集可以让您对程序的行为有更多的保证,这使您可以更自由地组合它们 .

    当您使用函数式语言时,创建新函数通常与编写密切相关的函数一样简单 .

    all = partial(apply, and)
    

    如果您没有明确控制函数的全局依赖关系,这并不简单,甚至可能不可能 . 函数式编程的最佳特性是,您可以始终如一地创建更通用的抽象,并相信它们可以组合成更大的整体 .

  • 11

    在程序范例中(我应该说“结构化编程”吗?),你已经共享了可变内存和指令,它们以某种顺序(一个接一个地)读/写它 .

    在功能范例中,您有变量和函数(在数学意义上:变量不随时间变化,函数只能根据输入计算某些东西) .

    (这是过于简单化的,例如,FPL通常具有处理可变内存的工具,而程序语言通常可以支持更高阶的程序,所以事情并不那么明确;但这应该给你一个想法)

  • 12

    来自IBM DeveloperworksCharming Python: Functional programming in Python真的帮助我理解了它的不同之处 .

    特别是对于那些熟悉Python的人来说,本文中的代码示例在功能和程序上做了不同的对比,可以澄清程序和函数式编程之间的区别 .

  • 2

    在函数式编程中,为了推理符号(变量或函数名称)的含义,您只需要知道两件事 - 当前范围和符号名称 . 如果你有一个具有不变性的纯功能语言,这些语言都是“静态的”(对于严重超载的名称而言遗憾)概念,这意味着只需查看源代码就可以看到 - 当前范围和名称 .

    在程序编程中如果你想回答问题背后的 Value 是什么,你还需要知道你是如何到达那里的,仅凭范围和名称是不够的 . 这就是我认为最大的挑战,因为这个执行路径是一个"runtime"属性,并且可以依赖于许多不同的东西,大多数人学习只调试它而不是尝试恢复执行路径 .

  • 45

    我最近一直在考虑Expression Problem的区别 . Phil Wadler's description经常被引用,但this question的接受答案可能更容易理解 . 基本上,似乎命令式语言倾向于选择一种方法来解决问题,而功能语言倾向于选择另一种方法 .

相关问题