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

Phi*_*her 110 procedural-programming programming-languages functional-programming

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

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

在计算机科学中,函数式编程是一种编程范式,它将计算视为数学函数的评估,并避免状态和可变数据.它强调功能的应用,与强调状态变化的命令式编程风格形成对比.函数式编程的根源在于lambda演算,这是一种在20世纪30年代开发的用于研究函数定义,函数应用和递归的正式系统.许多函数式编程语言可以被视为lambda演算的详细说明.

在实践中,数学函数与命令式编程中使用的"函数"概念之间的区别在于命令式函数可能具有副作用,从而改变程序状态的值.因此,它们缺乏参照透明度,即相同的语言表达式可能在不同的时间导致不同的值,这取决于执行程序的状态.相反,在函数代码中,函数的输出值仅取决于输入到函数的参数,因此f使用相同的参数值调用函数 两次x将产生相同的结果f(x).消除副作用可以更容易理解和预测程序的行为,这是开发函数式编程的关键动机之一.

在第2段中,它说

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

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

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

WRe*_*ach 269

功能编程

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

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

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

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

程序编程

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

对比

这两种风格并不是完全相反的 - 它们彼此不同.有些语言完全包含这两种风格(例如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;
}
Run Code Online (Sandbox Code Playgroud)

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

function allOdd(words) {
  return apply(and, map(compose(odd, length), words));
}
Run Code Online (Sandbox Code Playgroud)

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

  1. compose(odd, length)组合oddlength函数以产生一个新函数,确定字符串的长度是否为奇数.
  2. map(..., words)调用每个元素的新函数words,最终返回一个新的布尔值列表,每个列表指示相应的字是否具有奇数个字符.
  3. apply(and, ...)将"and"运算符应用于结果列表,并将所有布尔值组合在一起以产生最终结果.

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

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

进一步阅读

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

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

编程可以从冯诺依曼风格中解放出来吗?

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


附录 - 2013年

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

  • 查询(例如列表推导,语言集成查询)
  • 数据流(例如隐式迭代,批量操作)
  • 面向对象的(例如封装数据和方法)
  • 面向语言(例如特定于应用程序的语法,宏)

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

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


Ana*_*tts 45

功能和命令式编程之间的真正区别在于思维模式 - 命令式程序员正在考虑变量和内存块,而函数式程序员正在思考,"如何输入数据转换为输出数据" - 您的"程序"就是管道和数据转换集,以便从输入到输出.这是IMO的有趣部分,而不是"你不应该使用变量"这一点.

由于这种心态的结果,FP程序通常描述什么会发生,而不是具体机制如何它会发生-这是强大的,因为如果我们能清楚地说明什么是"选择"和"去哪儿"和"聚合"的意思,我们可以自由地交换他们的实现,就像我们使用AsParallel()一样,突然我们的单线程应用程序扩展到n个核心.


And*_*mas 12

     Isn't that the same exact case for procedural programming?
Run Code Online (Sandbox Code Playgroud)

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

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

  • C中的rand()函数为每个调用提供不同的结果.它在调用之间存储状态.它不是引用透明的.相比之下,在给定相同参数的情况下,C++中的std :: max(a,b)将始终返回相同的结果,并且没有副作用(我知道...). (8认同)

Vee*_*rac 11

我不同意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;
}
Run Code Online (Sandbox Code Playgroud)

function allOdd(words) {
  return apply(and, map(compose(odd, length), words));
}
Run Code Online (Sandbox Code Playgroud)

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

  • 实用
  • 表达导向和
  • 迭代器中心

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

让我们快速谈谈这些.

以表达为中心的风格是事物尽可能地评估事物的风格.虽然函数式语言因其对表达式的热爱而闻名,但实际上可能有一种没有可组合表达式的函数式语言.我要做一个,没有表情,只有陈述.

lengths: map words length
each_odd: map lengths odd
all_odd: reduce each_odd and
Run Code Online (Sandbox Code Playgroud)

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

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

def all_odd(words):
    lengths = (len(word) for word in words)
    each_odd = (odd(length) for length in lengths)
    return all(each_odd)
Run Code Online (Sandbox Code Playgroud)

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

当然,你可以压缩这个:

def all_odd(words):
    return all(odd(len(word)) for word in words)
Run Code Online (Sandbox Code Playgroud)

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

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

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

使用迭代器,您可以:

function allOdd(words) {
    for (word : words) { if (!odd(length(word))) { return false; } }
    return true;
}
Run Code Online (Sandbox Code Playgroud)

那么,什么功能性语言的点,如果偏差在:

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


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

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

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

all = partial(apply, and)
Run Code Online (Sandbox Code Playgroud)

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


小智 6

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

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

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