功能思维。 第一部分

在本系列文章中,您将熟悉函数式编程的基本原理,并了解“函数式思考”的含义以及这种方法与面向对象或命令式编程的区别。




既然您已经在“ 深入F#中”一文中看到了使用F#的一些原因 《 C#开发人员指南 》,退后一步,讨论函数式编程的基础。 “函数式编程”的真正含义是什么,这种方法与面向对象或命令式编程有何不同?


改变观念(简介)


重要的是要理解函数式编程不仅仅是一种单独的编程样式。 这是一种完全不同的编程思维方式,与“传统”方法不同,因为当前的OOP(采用Smalltalk风格)与传统的命令式语言(如C)有所不同。


F#允许您使用非功能性编码样式,这会诱使程序员保留其现有习惯。 您可以使用F#进行编程,而无需从根本上改变世界观,甚至不知道您错过了什么。 但是,为了充分利用F#并学习如何自信地以功能性风格进行编程,学会以功能性而非强制性的方式思考非常重要。


本系列文章的目的是帮助读者理解函数式编程的背景并改变其思维方式。

尽管我将使用许多简短的代码示例来说明一些要点,但这将是一个非常抽象的系列。 我们将涵盖以下主题:


  • 数学函数 。 第一篇文章介绍了功能语言基础的数学概念以及此方法带来的好处。
  • 功能和价值 。 下面介绍函数和值,解释“值”与变量的区别,以及函数和简单值之间的相似之处。
  • 类型 。 然后,我们继续介绍与函数一起使用的主要类型:原始类型,例如字符串和整数,单元类型,函数类型和泛型。
  • 具有几个参数的功能 。 我将进一步解释“ currying”和“ partial application”的概念。 在这个地方,某人的大脑会受到伤害,特别是如果这些大脑只有当下的过去。
  • 功能定义 。 然后,将有几篇文章专门讨论定义和组合功能的许多不同方式。
  • 功能签名 。 以下是有关函数签名的关键价值,它们的含义以及如何使用签名来理解函数内容的重要文章。
  • 职能组织 。 当清楚如何创建函数时,就会出现一个问题:如何组织它们以使其余代码可以访问它们?

数学函数


函数式编程受数学启发。 数学函数具有许多功能语言试图实现的非常好的功能。


让我们从将数字加1的数学函数开始。


Add1(x) = x+1 

这个表达的真正含义是什么? 看起来很简单。 这意味着存在这样的操作:将数字加1。
添加一些术语:


  • 有效输入函数值的集合称为 (作用域)。 在此示例中,可能有很多实数,但是我们将使生活变得更简单,并将自己局限于整数。
  • 函数的可能结果集(值范围)称为范围 (从技术上讲,共图像)。 在这种情况下,也有很多整数。
  • 一个函数称为从域到范围的映射 (在原始映射中 )。 (即,从范围到范围。)


这就是该定义在F#中的外观。


 let add1 x = x + 1 

如果在F#Interactive中输入它(不要忘记双分号),那么您可以看到结果(函数的“签名”):


 val add1 : int -> int 

详细考虑结论:


  • 一般含义是add1函数将整数(来自定义域)与整数(来自值范围)进行比较。
  • add1 ”被定义为“ val”,“ value”的缩写。 嗯? 这是什么意思 我们将在稍后讨论含义。
  • 箭头符号“->”用于显示域和范围。 在这种情况下,域的类型为“ int”,例如range。

请注意,没有明确指定类型,但是F#编译器确定该函数可与ints一起使用。 (可以更改吗?是的,很快我们将看到它)。


数学函数的关键属性


数学函数具有许多特性,这些特性与程序编程中使用的函数有很大区别。


  • 对于相同的输入值,函数始终具有相同的结果。
  • 该功能没有副作用。

这些特性提供了许多显着的优点,功能编程语言试图在其设计中尽可能地实现这些优点。 我们将依次考虑它们。


数学函数总是将相同结果返回给定值


在命令式编程中,我们认为函数可以“做某事”或“计算某事”。 数学函数不计算任何东西,它是从输入到输出的纯映射。 实际上,函数的另一个定义是所有映射的简单集合。 例如,很可能将函数“ add1”(在C#中)定义为


 int add1(int input) { switch (input) { case 0: return 1; case 1: return 2; case 2: return 3; case 3: return 4; etc ad infinitum } } 

显然,不可能为每个可能的数字都包含一个大小写,但是原理是相同的。 使用此设置,不执行任何计算,仅执行搜索。


数学函数无副作用


在数学函数中,输入和输出值在逻辑上是两个不同的事物,都是预先定义的。 该功能不会更改输入或输出数据,而只是将预定义的输入值从定义区域映射到值区域中的预定义的输出值。


换句话说,函数计算不会对输入数据或任何其他类型产生任何影响 。 应当记住,函数的计算并没有真正计数或操纵任何东西,而只是高估了搜索。


值的这种“不变性”非常微妙,但同时也非常重要。 当我做数学运算时,我并不期望数字会随着添加而改变。 例如,如果我有:


 x = 5 y = x+1 

我不希望x加1时x改变。我希望得到一个不同的数字( y ),并且x应该保持不变。 在数学世界中,整数已经存在于不可变集中,并且add1函数仅确定它们之间的关系。


纯功能的力量


那些具有可重复结果且没有副作用的函数称为“纯函数”,您可以使用它们来做一些有趣的事情:


  • 它们很容易并行化。 假设我们可以取一个介于1到1000之间的整数,然后将它们分配给1000个不同的处理器,然后我们指示每个CPU对相应的数字执行“ add1 ”,同时确保它们之间不需要任何交互。 没有锁,没有互斥体,没有信号量,等等。
  • 您可以延迟使用函数,并在程序逻辑需要时对其进行计算。 您可以确定答案是完全相同的,而不管是现在还是以后进行计算。
  • 您只能对特定的输入执行函数计算,然后缓存结果,因为已知这些输入值将提供相同的输出。
  • 如果有许多纯函数,则可以按任何顺序计算它们。 同样,这不会影响最终结果。

因此,如果可以用编程语言创建纯函数,则可以立即收到许多强大的技巧。 毫无疑问,所有这些都可以在F#中完成:



数学函数的“无用”属性


数学函数还具有一些在编程时似乎不太有用的属性。


  • 输入和输出值是不可变的
  • 函数始终具有一个输入和一个输出。

这些属性反映在功能编程语言的设计中。 值得单独考虑它们。


输入和输出值是不可变的


理论上不可变的值似乎是个好主意,但是如果没有传统方法分配变量的方法,那么如何真正完成工作。


我可以向您保证,这不是您可以想象的大问题。 在本系列文章中,将清楚地了解这在实践中是如何工作的。


数学函数始终具有一个输入和一个输出。


从图中可以看出,对于一个数学函数,总是只有一个输入,只有一个输出。 对于函数式编程语言,这也是正确的,尽管在首次使用时可能并不明显。
这似乎带来了很大的不便。 没有带有两个(或多个)参数的函数,如何做一些有用的事情?


事实证明,有一种方法可以做到这一点,而且,它在F#上是完全透明的。 它被称为“咖喱”,并有一个单独的帖子,该帖子将在不久的将来出现。


实际上,稍后将变得很清楚,这两个“无用”的属性将变得难以置信的价值,并将成为使函数式编程如此强大的关键部分。


其他资源


F#的教程很多,包括那些具有C#或Java经验的人的材料。 当您深入了解F#时,以下链接可能会很有用:



还介绍了其他几种开始学习F#的方法


最后,F#社区非常适合初学者。 在Slack上,由F#Software Foundation支持的聊天非常活跃,您可以自由加入初学者室。 我们强烈建议您这样做!


不要忘记访问俄语社区F#的网站 ! 如果您对学习语言有任何疑问,我们将很乐意在聊天室中讨论这些问题:



关于翻译作者


@kleidemos翻译
在F#开发人员俄语社区的努力下进行了翻译和编辑更改。 我们也感谢@schvepsss@shwars为本文准备发表。

Source: https://habr.com/ru/post/zh-CN415189/


All Articles