【问题标题】:Can somebody please explain in layman terms what a functional language is? [duplicate]有人可以用外行术语解释什么是功能语言吗? [复制]
【发布时间】:2012-01-30 18:12:42
【问题描述】:

可能重复:
Functional programming and non-functional programming

恐怕维基百科没有给我带来任何进一步的信息。 非常感谢

PS:这个过去的帖子也很好,但是我很高兴我再次问了这个问题,因为新的答案很棒 - 谢谢

Functional programming and non-functional programming

【问题讨论】:

  • 当您指的是基础知识时,请问您到底是什么意思?我认为学习不同的范式是基础..
  • Lion 的回答和响应是堆栈溢出用户不理解“外行”含义的明显示例。如果有人看到这是新的并且不知所措或气馁,请不要担心,在其他地方搜索更明确的术语。

标签: functional-programming


【解决方案1】:

首先了解Turing machine 是什么(来自维基百科)。

图灵机是一种根据规则表对一条带子上的符号进行操作的设备。尽管图灵机很简单,但它可以用来模拟任何计算机算法的逻辑,并且在解释计算机内部 CPU 的功能时特别有用。

这是关于Lamda calculus.(来自维基百科)。

在数理逻辑和计算机科学中,λ 演算,也写作 λ-演算,是用于研究可计算递归函数、可计算性理论以及变量绑定和替换等相关现象的形式系统。

函数式编程语言使用 lambda 演算作为其基本计算模型,而所有其他编程语言都使用图灵机作为其基本计算模型。 (好吧,从技术上讲,我应该说函数式编程语言 vr.s imperative programming 语言 - 因为其他范式中的语言使用其他模型。例如,SQL 使用关系模型,Prolog 使用逻辑模型等等。但是,漂亮人们在讨论编程语言时实际上考虑的所有语言都是函数式或命令式的,所以我会坚持简单的概括。)

“计算的基本模型”是什么意思?好吧,所有语言都可以分为两层:一层,一些核心图灵完备的语言,然后是抽象层或语法糖(取决于你是否喜欢它们),它们是根据基本图灵定义的——完整的语言。命令式语言的核心语言是经典图灵机计算模型的一种变体,可以称为“C 语言”。在这种语言中,内存是可以读取和写入的字节数组,并且您有一个或多个 CPU 来读取内存、执行简单的算术、条件分支等。这就是我所说的这些语言的基本计算模型是图灵机。

函数式语言的基本计算模型是 Lambda 演算,它以两种不同的方式出现。 首先,许多函数式语言所做的一件事是明确地根据 lambda 演算的转换来编写它们的规范,以指定用该语言编写的程序的行为(这被称为“指称语义”)。而且第二,几乎所有函数式编程语言都实现了它们的编译器以使用显式的类似 lambda 演算的中间语言 - Haskell 有 Core,Lisp 和 Scheme 有它们的“去糖”表示(毕竟宏已被应用),Ocaml (Objective Categorical Abstract Machine Language) 有它的 lispish 中间表示,等等。

那么我一直在讨论的这个 lambda 演算是什么?嗯,基本思想是,要进行任何计算,你只需要两件事。你需要的第一件事是函数抽象——一个未命名的、单参数的函数的定义。最早定义 Lambda 演算的 Alonzo Church 使用相当晦涩的符号将函数定义为希腊字母 lambda,然后是函数参数的单字符名称,然后是句点,然后是表达式函数体。因此,给定任何值的恒等函数仅返回该值,看起来像“λx.x” 我将使用更易于人类阅读的方法 - 我将用单词“替换 λ 字符” fun”,句号带“->”,允许空格,允许多字符名称。所以我可以把恒等函数写成“fun x -> x”,甚至是“fun whatever -> whatever”。符号的变化不会改变基本性质。请注意,这是 Haskell 和 Lisp 等语言中引入未命名局部函数的“lambda 表达式”名称的来源。

您在 Lambda 演算中唯一可以做的另一件事是调用函数。您通过向函数应用参数来调用函数。我将遵循标准约定,即应用程序只是连续的两个名称——因此f x 将值x 应用于名为 f 的函数。如果需要,我们可以将 f 替换为其他表达式,包括 Lambda 表达式,并且我们可以将参数应用于表达式时,将应用程序替换为函数的主体,并替换所有出现的参数名称应用任何值。所以表达式(fun x -> x x) y 变成了y y

理论家们不遗余力地通过“用应用的值替换所有出现的变量”来精确定义他们的意思,并且可以继续详细说明它的工作原理(抛出诸如“阿尔法重命名”之类的术语”),但最终事情会完全按照您的预期进行。表达式 (fun x -> x x) (x y) 变为 (x y) (x y) - 匿名函数中的参数 x 与正在应用的值中的 x 之间没有混淆。这甚至在多个层次上也有效——表达式(fun x -> (fun x -> x x)) (x x)) (x y)首先变为(fun x -> x x) ((x y) (x y)),然后变为((x y) (x y)) ((x y) (x y))。最内层函数(“(fun x -> x x)”) 中的 x 与其他 x 不同。

将函数应用程序视为字符串操作是完全正确的。如果我有一个 (fun x -> some 表达式),并且我对其应用了一些值,那么结果只是某个表达式,所有 x 在文本上都替换为“某个值”(除了那些被另一个参数遮蔽的表达式) )。

顺便说一句,我将在需要消除歧义的地方添加括号,并在不需要的地方省略它们。它们唯一的区别是分组,没有其他意义。

这就是 Lambda 演算的全部内容。不,真的,这只是匿名函数抽象和函数应用程序。我可以看出您对此表示怀疑,所以让我来解决您的一些担忧。

首先,我指定一个函数只接受一个参数 - 你如何拥有一个接受两个或更多参数的函数?简单——你有一个接受一个参数的函数,并返回一个接受第二个参数的函数。例如,函数组合可以定义为fun f -> (fun g -> (fun x -> f (g x)))——将其读作一个接受参数 f 的函数,并返回一个接受参数 g 的函数,并返回一个接受参数 x 并返回 f (gx) 的函数。

那么我们如何仅使用函数和应用程序来表示整数?很容易(如果不是很明显)——例如,第一个是函数fun s -> fun z -> s z——给定一个“后继”函数 s 和一个“零”z,然后一个是零的后继。二是fun s->fun z -> s s z,后继为0,三是fun s -> fun z -> s s s z,以此类推。

将两个数字相加,比如xy,虽然很微妙,但也很简单。加法函数就是fun x -> fun y -> fun s -> fun z -> x s (y s z)。这看起来很奇怪,所以让我通过一个例子来证明它确实有效——让我们将数字 3 和 2 相加。现在,三只是 (fun s -> fun z -> s s s z),二只是 (fun s -> fun z -> s s z),所以我们得到(每一步都将一个参数应用于一个函数,没有特定的顺序):

(fun x -> fun y -> fun s -> fun z -> x s (y s z)) (fun s -> fun z -> s s s z) (fun s -> fun z -> s s z)

(fun y -> fun s -> fun z -> (fun s -> fun z -> s s s z) s (y s z)) (fun s -> fun z -> s s z)

(fun y -> fun s -> fun z -> (fun z -> s s s z) (y s z)) (fun s -> fun z -> s s z)

(fun y -> fun s -> fun z -> s s s (y s z)) (fun s -> fun z -> s s z)

(fun s -> fun z -> s s s ((fun s -> fun z -> s s z) s z))

(fun s -> fun z -> s s s (fun z -> s s z) z)

(fun s -> fun z -> s s s s s z) 

最后,我们得到了一个不出所料的答案,即继任者对继任者的继任者对继任者的继任者对继任者的继任者为零,更通俗地称为五。加法的工作原理是用 y 值替换 x 值的 zero(或我们开始计数的地方)来定义乘法,而是使用“后继”的概念:

(fun x -> fun y -> fun s -> fun z -> x (y s) z)

我会让你验证上面的代码是否正确

Wikipedia says

命令式程序倾向于强调程序在执行操作时所采取的一系列步骤,而函数式程序倾向于强调函数的组合和排列,通常不指定明确的步骤。一个简单的例子说明了这一点,用两个解决方案来实现相同的编程目标(计算斐波那契数)。命令式示例是用 C++ 编写的。

// Fibonacci numbers, imperative style
int fibonacci(int iterations)
{
    int first = 0, second = 1; // seed values

    for (int i = 0; i < iterations; ++i) {
        int sum = first + second;
        first = second;
        second = sum;
    }

    return first;
}

std::cout << fibonacci(10) << "\n";

功能版本(Haskell)有不同的感觉:

-- Fibonacci numbers, functional style

-- describe an infinite list based on the recurrence relation for Fibonacci numbers
fibRecurrence first second = first : fibRecurrence second (first + second)

-- describe fibonacci list as fibRecurrence with initial values 0 and 1
fibonacci = fibRecurrence 0 1

-- describe action to print the 10th element of the fibonacci list
main = print (fibonacci !! 10)

See this PDF also

【讨论】:

  • 你的答案显然比我的好,+1 并删除了我的。
  • 很好的解释,我想我得到了这种声明性与过程性的区别,面向对象编程如何适应这张图片?
  • 这个 PDF 也可以帮助你...jot.fm/issues/issue_2009_09/article5.pdf
  • 这些链接很棒,谢谢你可以公平地说命令式 - 与 - 功能 - 与 - 面向对象编程代表三种不同的范式,没有重叠(除非多范式语言将它们结合起来)?
【解决方案2】:

(A) 函数式编程机械地描述解决方案。您定义了一台不断正确输出的机器,例如Caml:

let rec factorial = function
  | 0 -> 1
  | n -> n * factorial(n - 1);;

(B) 过程式编程在时间上描述解决方案。您描述了将给定输入转换为正确输出的一系列步骤,例如使用 Java:

int factorial(int n) {
  int result = 1;
  while (n > 0) {
    result *= n--;
  }
  return result;
}

函数式编程语言希望你一直这样做与它们的用途无关。

【讨论】:

  • 只是为了清除。我没有对你的帖子投任何票。不上也不下。
猜你喜欢
  • 2011-04-19
  • 2023-03-14
  • 2015-04-09
  • 2020-04-20
  • 1970-01-01
  • 1970-01-01
  • 2022-01-22
  • 1970-01-01
相关资源
最近更新 更多