functional-programming - 有人可以用通俗的语言解释什么是函数式语言吗?

标签 functional-programming

这个问题在这里已经有了答案:




9年前关闭。




Possible Duplicate:
Functional programming and non-functional programming



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

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

Functional programming and non-functional programming

最佳答案

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

A Turing machine is a device that manipulates symbols on a strip of tape according to a table of rules. Despite its simplicity, a Turing machine can be adapted to simulate the logic of any computer algorithm, and is particularly useful in explaining the functions of a CPU inside a computer.



这大约是 Lamda calculus.(来自维基百科)。

In mathematical logic and computer science, the lambda calculus, also written as the λ-calculus, is a formal system for studying computable recursive functions, a la computability theory, and related phenomena such as variable binding and substitution.



函数式编程语言使用 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 what -> what”。符号的变化不会改 rebase 本性质。请注意,这是引入未命名本地函数的 Haskell 和 Lisp 表达式等语言中名称“lambda 表达式”的来源。

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

理论家们竭尽全力通过“用应用的值替换所有出现的变量”来精确定义他们的意思,并且可以详细说明这是如何精确工作的(抛出诸如“alpha重命名”之类的术语),但最终事情会像你期望的那样工作。表达式 (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 expression),并且我对其应用了一些值,那么结果只是一些表达式,其中所有 x 的文本都被替换为“某个值”(除了那些被另一个参数遮蔽的值) )。

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

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

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

那么我们如何仅使用函数和应用程序来表示整数呢?很容易(如果不是很明显)——例如,第一个函数是函数 fun s -> fun z -> s z——给定一个“后继”函数 s 和一个“零”z,那么 1 就是 0 的后继函数。二是 fun s -> fun z -> s s z ,后继者为零,三是 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) 

最后,我们得到了毫不奇怪的答案,即继承者的继承者的继承者的继承者的继承者为零,更通俗地称为五。加法的工作原理是将 zero 值的 x(或我们开始计数的位置)替换为 y 值 - 为了定义乘法,我们改为使用“后继”的概念:
(fun x -> fun y -> fun s -> fun z -> x (y s) z)

我会让你来验证上面的代码

Wikipedia says

Imperative programs tend to emphasize the series of steps taken by a program in carrying out an action, while functional programs tend to emphasize the composition and arrangement of functions, often without specifying explicit steps. A simple example illustrates this with two solutions to the same programming goal (calculating Fibonacci numbers). The imperative example is in 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

关于functional-programming - 有人可以用通俗的语言解释什么是函数式语言吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/8682893/

相关文章:

Java 和函数式编程范例 - 如何使我的数组不可变

scala - 如果scala提倡不变性,为什么它采用具有可变性的actor模型?

scala - 在 Scala 中只坚持函数范式的功效

javascript - 将json转换为其他json结构

functional-programming - Elixir 中带有计数器的无限循环

algorithm - 每个元素在列表中出现的次数

haskell - 从字典和维度生成所有可能组合的功能性尾递归方式

Haskell Lambda 帮助 - 从 lambda 术语输入中拆分术语

oracle - Oracle 中的功能索引交叉引用

python - 如何在 reduce() 中引用整个数组?