- Erlang 教程
- Erlang - 首页
- Erlang - 概述
- Erlang - 环境
- Erlang - 基本语法
- Erlang - Shell
- Erlang - 数据类型
- Erlang - 变量
- Erlang - 运算符
- Erlang - 循环
- Erlang - 决策
- Erlang - 函数
- Erlang - 模块
- Erlang - 递归
- Erlang - 数字
- Erlang - 字符串
- Erlang - 列表
- Erlang - 文件 I/O
- Erlang - 原子
- Erlang - 映射
- Erlang - 元组
- Erlang - 记录
- Erlang - 异常
- Erlang - 宏
- Erlang - 头文件
- Erlang - 预处理器
- Erlang - 模式匹配
- Erlang - 保护条件
- Erlang - BIFS (内置函数)
- Erlang - 二进制
- Erlang - Fun (匿名函数)
- Erlang - 进程
- Erlang - 邮件
- Erlang - 数据库
- Erlang - 端口
- Erlang - 分布式编程
- Erlang - OTP (开放电信平台)
- Erlang - 并发
- Erlang - 性能
- Erlang - 驱动程序
- Erlang - Web编程
- Erlang 有用资源
- Erlang 快速指南
- Erlang - 有用资源
- Erlang - 讨论
Erlang 快速指南
Erlang - 概述
Erlang 是一种函数式编程语言,同时也是一个运行时环境。它的设计具有对并发、分布式和容错的集成支持。Erlang 最初是为爱立信的多个大型电信系统而开发的。
Erlang 的第一个版本由 Joe Armstrong、Robert Virding 和 Mike Williams 于 1986 年开发。它最初是爱立信内部的专有语言。后来于 1998 年发布为开源语言。Erlang 以及 OTP(Erlang 中的一套中间件和库)现在由爱立信的 OTP 产品部门支持和维护,通常被称为 **Erlang/OTP**。
为什么选择 Erlang?
如果您有以下需求,则应使用 Erlang 开发您的应用程序:
应用程序需要处理大量并发活动。
它应该易于分布在计算机网络上。
应该具备使应用程序能够容忍软件和硬件错误的功能。
应用程序应该是可扩展的。这意味着它应该能够跨多个服务器运行,而几乎无需更改。
它应该易于升级和重新配置,而无需停止和重新启动应用程序本身。
应用程序应该在某些严格的时间范围内对用户做出响应。
Erlang 的官方网站是 https://erlang.org.cn/。
Erlang - 环境
现在,在您可以开始使用 Erlang 之前,您需要确保您的系统上运行着功能齐全的 Erlang 版本。本节将介绍在 Windows 机器上安装 Erlang 及其后续配置,以便开始使用 Erlang。
在继续安装之前,请确保满足以下系统要求。
系统要求
内存 | 2 GB RAM(推荐) |
---|---|
磁盘空间 | 无最低要求。最好有足够的存储空间来存储将使用 Erlang 创建的应用程序。 |
操作系统版本 | Erlang 可以安装在 Windows、Ubuntu/Debian、Mac OS X 上。 |
下载 Erlang
要下载 Erlang,必须访问以下网址:www.erlang.org/downloads。
此页面提供各种下载以及在 Linux 和 Mac 平台上下载和安装该语言所需的步骤。
单击“OTP 18.3 Windows 32 位二进制文件”开始下载 Erlang Windows 安装文件。
Erlang 安装
以下步骤详细说明如何在 Windows 上安装 Erlang:
**步骤 1** - 启动在前面部分下载的安装程序。安装程序启动后,单击“运行”。
**步骤 2** - 单击下一个屏幕上的“下一步”以接受将安装的默认组件。
**步骤 3** - 接受默认安装路径并单击“下一步”。
**步骤 4** - 接受将创建的默认“开始”菜单项,然后单击“下一步”。
**步骤 5** - 安装完成后,单击“关闭”以完成安装。
Erlang 配置
安装完成后,需要进行以下配置以确保 Erlang 在系统上开始工作。
操作系统 | 输出 |
---|---|
Windows | 将字符串;C:\Program Files(x86)\erl7.2.1\bin 或 C:\Program Files\erl7.2.1\bin 附加到系统变量 PATH 的末尾。 |
如果您现在打开命令提示符并键入 **erl**,您应该能够获得 erl 命令提示符。
恭喜,您现在已在笔记本电脑上成功配置了 erl。
在流行的 IDE 上安装插件
Erlang 作为一种编程语言,也适用于流行的 IDE,例如 **Eclipse 和 IntelliJ**。让我们看看如何在这些 IDE 中获取所需的插件,以便您在使用 Erlang 时有更多选择。
在 Eclipse 中安装
**步骤 1** - 打开 Eclipse 并单击菜单项 **帮助 → 安装新软件**。
**步骤 2** - 将“使用”链接输入为 https://download.erlide.org/update
然后单击“添加”。
**步骤 3** - 然后系统将提示您为插件输入名称,请输入名称为 **Erlide**。单击“确定”。
**步骤 4** - Eclipse 将扫描提供的链接并获取所需的插件。选中插件并单击“下一步”。
**步骤 5** - 在下一个对话框中,Eclipse 将显示所有将安装的组件。单击“下一步”。
**步骤 6** - 在下一个对话框中,Eclipse 只需您查看正在安装的组件。单击“下一步”。
**步骤 7** - 在下一个对话框中,您只需要接受许可协议。最后,单击“完成”按钮。
然后将开始安装,安装完成后,它将提示您重新启动 Eclipse。
重新启动 Eclipse 后,当您创建项目时,您也将能够看到 Erlang 作为选项。
在 IntelliJ 中安装
请按照以下步骤在您的计算机上安装 IntelliJ。
**步骤 1** - 打开 IntelliJ 并单击“配置”→“插件”。
**步骤 2** - 在搜索框中键入 Erlang。您将在屏幕右侧看到 Erlang 插件。单击“安装”按钮。
**步骤 3** - 安装 Erlang 插件后,系统将提示您重新启动 IDE。
重新启动 IDE 并尝试创建新项目时,您将看到创建 Erlang 项目的选项。
Erlang - 基本语法
为了理解 Erlang 的基本语法,让我们首先看看一个简单的 **Hello World** 程序。
示例
% hello world program -module(helloworld). -export([start/0]). start() -> io:fwrite("Hello, world!\n").
关于上述程序,需要注意以下几点:
% 符号用于向程序添加注释。
module 语句就像在任何编程语言中添加命名空间一样。因此,在这里,我们提到这段代码将是名为 **helloworld** 的模块的一部分。
export 函数用于定义程序中任何可以使用的函数。我们定义了一个名为 start 的函数,为了使用 start 函数,我们必须使用 export 语句。** /0 ** 表示我们的函数“start”接受 0 个参数。
我们最终定义了 start 函数。在这里,我们使用了另一个名为 **io** 的模块,其中包含 Erlang 中所有必需的输入输出函数。我们使用 **fwrite** 函数将“Hello World”输出到控制台。
上述程序的输出将是:
输出
Hello, world!
语句的一般形式
在 Erlang 中,您已经看到在 Erlang 语言中使用了不同的符号。让我们通过一个简单的 Hello World 程序来了解我们所看到的:
连字符符号 **(-)** 通常与 module、import 和 export 语句一起使用。连字符符号用于为每个语句赋予相应的含义。因此,来自 Hello world 程序的示例如下所示:
-module(helloworld). -export([start/0]).
每个语句都以点 **(.)** 符号分隔。Erlang 中的每个语句都必须以这个分隔符结尾。来自 Hello world 程序的示例如下所示:
io:fwrite("Hello, world!\n").
斜杠 **(/)** 符号与函数一起使用,用于定义函数接受的参数数量。
-export([start/0]).
模块
在 Erlang 中,所有代码都分为模块。模块由一系列属性和函数声明组成。它就像其他编程语言中的命名空间概念一样,用于逻辑上分离不同的代码单元。
定义模块
模块是用模块标识符定义的。一般语法和示例如下。
语法
-module(ModuleName)
**ModuleName** 必须与文件名相同,只是缺少扩展名 **.erl**。否则,代码加载将无法按预期工作。
示例
-module(helloworld)
这些模块将在后续章节中详细介绍,这只是为了让您对如何定义模块有一个基本的了解。
Erlang 中的 import 语句
在 Erlang 中,如果要使用现有的 Erlang 模块的功能,可以使用 import 语句。import 语句的一般形式如下图所示:
示例
-import (modulename, [functionname/parameter]).
其中:
**Modulename** - 这是需要导入的模块的名称。
**functionname/parameter** - 需要导入的模块中的函数。
让我们更改编写 hello world 程序的方式以使用 import 语句。示例如下所示:
示例
% hello world program -module(helloworld). -import(io,[fwrite/1]). -export([start/0]). start() -> fwrite("Hello, world!\n").
在上面的代码中,我们使用 import 关键字导入库“io”以及 **fwrite** 函数。因此,每当我们调用 fwrite 函数时,就不必在任何地方都提及 **io** 模块名称。
Erlang 中的关键字
关键字是 Erlang 中的保留字,不应将其用于与其预期用途不同的任何其他用途。以下是 Erlang 中的关键字列表。
after | and | andalso | band |
begin | bnot | bor | bsl |
bsr | bxor | case | catch |
cond | div | end | fun |
if | let | not | of |
or | orelse | receive | rem |
try | when | xor |
Erlang 中的注释
注释用于记录您的代码。单行注释通过在行的任何位置使用 **%** 符号来标识。以下是一个示例:
示例
% hello world program -module(helloworld). % import function used to import the io module -import(io,[fwrite/1]). % export function used to ensure the start function can be accessed. -export([start/0]). start() -> fwrite("Hello, world!\n").
Erlang - Shell
Erlang shell 用于表达式测试。因此,在应用程序中实际测试之前,可以在 shell 中非常轻松地进行测试。
以下示例展示了如何在 shell 中使用加法表达式。需要注意的是,表达式需要以点(.)分隔符结尾。
命令执行后,shell 会打印另一个提示符,这次是命令编号 2(因为每次输入新命令时,命令编号都会递增)。
以下函数是 Erlang shell 中最常用的函数。
b() − 打印当前变量绑定。
语法 − b()。
例如 − 下面是一个函数使用方法的示例。首先定义一个名为 Str 的变量,其值为 abcd。然后使用 b() 显示所有绑定的变量。
f() − 删除所有当前变量绑定。
语法 − f()。
例如 − 下面是一个函数使用方法的示例。首先定义一个名为 Str 的变量,其值为 abcd。然后使用 f() 删除 Str 变量绑定。然后调用 b() 以确保绑定已成功删除。
f(x) − 删除特定变量的绑定。
语法 − f(x)。其中,x – 是需要删除其绑定的变量。
例如 − 下面是一个函数使用方法的示例。首先定义名为 Str 和 Str1 的变量。然后使用 f(Str) 删除 Str 变量绑定。然后调用 b() 以确保绑定已成功删除。
h() − 打印 shell 中执行的所有命令的历史列表。
语法 − h()。
例如 − 下面的屏幕截图显示了 h() 命令的示例,该命令打印在 shell 中执行的命令历史记录。
history(N) − 将历史列表中保留的先前命令数量设置为 N。返回之前的数量。默认数量为 20。
语法 − history(N)。其中,N – 是命令历史列表需要限制到的数量。
例如 − 下面的屏幕截图显示了 history(N) 命令的示例。
e(N) − 如果 N 为正数,则重复命令 N。如果为负数,则重复第 N 个先前命令(即,e(-1) 重复先前命令)。
语法 − e(N)。其中,N – 是列表中第 N 个位置的命令。
例如 − 下面显示了 e(N) 命令的示例。由于我们已执行 e(-1) 命令,它将执行之前的命令 history(5)。
Erlang - 数据类型
在任何编程语言中,都需要使用多个变量来存储各种类型的信息。变量只不过是保留的内存位置,用于存储值。这意味着,当创建变量时,会保留一些内存空间来存储与该变量关联的值。
您可能希望存储各种数据类型的信息,例如字符串、字符、宽字符、整数、浮点数、布尔值等。根据变量的数据类型,操作系统会分配内存并决定可以在保留的内存中存储什么。
内置数据类型
Erlang 提供了各种内置数据类型。以下是 Erlang 中定义的数据类型列表:
数字 − 在 Erlang 中,有两种类型的数字文字:整数和浮点数。
原子 − 原子是文字,是具有名称的常量。如果原子不以小写字母开头,或者包含除字母数字字符、下划线 (_) 或 @ 之外的其他字符,则需要将其括在单引号(')中。
布尔值 − Erlang 中的布尔数据类型是两个保留原子:true 和 false。
位串 − 位串用于存储未类型化内存区域。
元组 − 元组是一种复合数据类型,具有固定数量的项。元组中的每一项都称为元素。元素的数量称为元组的大小。
映射 − 映射是一种复合数据类型,具有可变数量的键值关联。映射中的每个键值关联都称为关联对。对的键和值部分称为元素。关联对的数量称为映射的大小。
列表 − 列表是一种复合数据类型,具有可变数量的项。列表中的每一项都称为元素。元素的数量称为列表的长度。
注意 − 您可能会惊讶地发现,在上面的列表中找不到字符串类型。这是因为 Erlang 中没有专门定义的字符串数据类型。但我们将在后续章节中了解如何使用字符串。
以下是每种数据类型的使用方法示例。同样,在接下来的章节中将详细讨论每种数据类型。这只是为了让您初步了解上述数据类型。
数字
以下程序显示了如何使用数字数据类型的示例。此程序显示了两个整数的加法。
示例
-module(helloworld). -export([start/0]). start() -> io:fwrite("~w",[1+1]).
上述程序的输出将是:
输出
2
原子
原子应以小写字母开头,可以包含小写和大写字符、数字、下划线 (_) 和“at”符号 (@)。我们也可以将原子括在单引号中。
以下程序显示了如何使用原子数据类型的示例。在此程序中,我们正在创建一个名为 atom1 的原子。
示例
-module(helloworld). -export([start/0]). start() -> io:fwrite(atom1).
上述程序的输出将是:
输出
atom1
布尔值
以下程序显示了如何使用布尔数据类型的示例。此示例比较两个整数,并将结果布尔值打印到控制台。
示例
-module(helloworld). -export([start/0]). start() -> io:fwrite(2 =< 3).
上述程序的输出将是:
输出
true
位串
以下程序显示了如何使用位串数据类型的示例。此程序定义了一个由 2 位组成的位串。binary_to_list 是 Erlang 中定义的内置函数,可用于将位串转换为列表。
示例
-module(helloworld). -export([start/0]). start() -> Bin1 = <<10,20>>, X = binary_to_list(Bin1), io:fwrite("~w",[X]).
上述程序的输出将是:
输出
[10,20]
元组
以下程序显示了如何使用元组数据类型的示例。
这里我们定义了一个包含 3 个项的 元组 P。tuple_size 是 Erlang 中定义的内置函数,可用于确定元组的大小。
示例
-module(helloworld). -export([start/0]). start() -> P = {john,24,{june,25}} , io:fwrite("~w",[tuple_size(P)]).
上述程序的输出将是:
输出
3
映射
以下程序显示了如何使用映射数据类型的示例。
这里我们定义了一个包含 2 个映射的 映射 M1。map_size 是 Erlang 中定义的内置函数,可用于确定映射的大小。
示例
-module(helloworld). -export([start/0]). start() -> M1 = #{name=>john,age=>25}, io:fwrite("~w",[map_size(M1)]).
上述程序的输出将是:
输出
2
列表
以下程序显示了如何使用列表数据类型的示例。
这里我们定义了一个包含 3 个项的 列表 L。length 是 Erlang 中定义的内置函数,可用于确定列表的大小。
示例
-module(helloworld). -export([start/0]). start() -> L = [10,20,30] , io:fwrite("~w",[length(L)]).
上述程序的输出将是:
输出
3
Erlang - 变量
在 Erlang 中,所有变量都使用 ‘=’ 语句绑定。所有变量都必须以大写字符开头。在其他编程语言中,‘=’ 符号用于赋值,但在 Erlang 中并非如此。如前所述,变量是使用 ‘=’ 语句定义的。
在 Erlang 中需要注意的一点是,变量是不可变的,这意味着为了更改变量的值,需要先销毁它,然后再重新创建。
最后一章解释了以下 Erlang 基本变量:
数字 − 用于表示整数或浮点数。例如 10。
布尔值 − 表示布尔值,可以是 true 或 false。
位串 − 位串用于存储未类型化内存区域。例如 <<40,50>>。
元组 − 元组是一种复合数据类型,具有固定数量的项。例如 {40,50}。
映射 − 映射是一种复合数据类型,具有可变数量的键值关联。映射中的每个键值关联都称为关联对。例如 {type=>person,age=>25}。
列表 − 列表是一种复合数据类型,具有可变数量的项。例如 [40,40]。
变量声明
定义变量的一般语法如下:
语法
var-name = var-value
其中:
var-name − 这是变量的名称。
var-value − 这是绑定到变量的值。
以下是一个变量声明的示例:
示例
-module(helloworld). -export([start/0]). start() -> X = 40, Y = 50, Result = X + Y, io:fwrite("~w",[Result]).
在上面的示例中,我们有两个变量,一个是 X,绑定到值 40,另一个是 Y,绑定到值 50。另一个名为 Result 的变量绑定到 X 和 Y 的和。
上述程序的输出将是:
输出
90
变量命名
如前所述,变量名必须以大写字母开头。让我们来看一下用小写字母声明的变量的示例。
示例
-module(helloworld). -export([start/0]). start() -> X = 40, Y = 50, result = X + Y, io:fwrite("~w",[Result]).
如果尝试编译上述程序,则会收到以下编译时错误。
输出
helloworld.erl:8: variable 'Result' is unbound
其次,所有变量只能赋值一次。让我们来看一下多次赋值变量的示例。
示例
-module(helloworld). -export([start/0]). start() -> X = 40, Y = 50, X = 60, io:fwrite("~w",[X]).
如果尝试编译上述程序,则会收到以下编译时错误。
输出
helloworld.erl:6: Warning: variable 'Y' is unused helloworld.erl:7: Warning: no clause will ever match helloworld.erl:7: Warning: the guard for this clause evaluates to 'false'
打印变量
在本节中,我们将讨论如何使用各种打印变量的函数。
使用 io:fwrite 函数
您可能已经在上述所有程序中看到过这个 (io:fwrite)。fwrite 函数是 Erlang 的“io”模块的一部分,可用于输出程序中变量的值。
以下示例显示了可与 fwrite 语句一起使用的更多参数。
示例
-module(helloworld). -export([start/0]). start() -> X = 40.00, Y = 50.00, io:fwrite("~f~n",[X]), io:fwrite("~e",[Y]).
上述程序的输出将是:
输出
40.000000 5.00000e+1
关于上述程序,应注意以下几点:
~ − 此字符表示需要对输出进行一些格式化。
~f − 参数是一个浮点数,写为 [-]ddd.ddd,其中精度是小数点后的位数。默认精度为 6,不能小于 1。
~n − 这是换行符 println。
~e − 参数是一个浮点数,写为 [-]d.ddde+-ddd,其中精度是写入的位数。默认精度为 6,不能小于 2。
Erlang - 运算符
运算符是告诉编译器执行特定数学或逻辑运算的符号。
Erlang 具有以下类型的运算符:
- 算术运算符
- 关系运算符
- 逻辑运算符
- 位运算符
算术运算符
Erlang 语言支持与任何其他语言相同的标准算术运算符。以下是 Erlang 中可用的算术运算符。
运算符 | 描述 | 示例 |
---|---|---|
+ | 两个操作数相加 | 1 + 2 结果为 3 |
- | 从第一个操作数中减去第二个操作数 | 1 - 2 结果为 -1 |
* | 两个操作数相乘 | 2 * 2 结果为 4 |
/ | 分子除以分母 | 2 / 2 结果为 1 |
rem | 第一个数字除以第二个数字的余数 | 3 rem 2 结果为 1 |
div | div 运算符将执行除法并返回整数部分。 | 3 div 2 结果为 1 |
关系运算符
关系运算符允许比较对象。以下是 Erlang 中可用的关系运算符。
运算符 | 描述 | 示例 |
---|---|---|
== | 测试两个对象是否相等 | 2 = 2 结果为真 |
/= | 测试两个对象是否不同 | 3 /= 2 结果为真 |
< | 检查左侧对象是否小于右侧操作数。 | 2 < 3 结果为真 |
=< | 检查左侧对象是否小于或等于右侧操作数。 | 2 =<3 结果为真 |
> | 检查左侧对象是否大于右侧操作数。 | 3 > 2 结果为真 |
>= | 检查左侧对象是否大于或等于右侧操作数。 | 3 >= 2 结果为真 |
逻辑运算符
这些逻辑运算符用于评估布尔表达式。以下是 Erlang 中可用的逻辑运算符。
运算符 | 描述 | 示例 |
---|---|---|
or | 这是逻辑“或”运算符 | true or true 结果为 true |
and | 这是逻辑“与”运算符 | true and false 结果为 false |
not | 这是逻辑“非”运算符 | not false 结果为 true |
xor | 这是逻辑异或运算符 | true xor false 结果为 true |
位运算符
Erlang 提供四个位运算符。以下是 Erlang 中可用的位运算符。
序号 | 运算符和描述 |
---|---|
1 |
band 这是按位“与”运算符 |
2 |
bor 这是按位“或”运算符 |
3 |
bxor 这是按位“异或”或“异或”运算符 |
4 |
bnot 这是按位取反运算符 |
以下是展示这些运算符的真值表:
p | q | p & q | p | q | p ^ q |
---|---|---|---|---|
0 | 0 | 0 | 0 | 0 |
0 | 1 | 0 | 1 | 1 |
1 | 1 | 1 | 1 | 0 |
1 | 0 | 0 | 1 | 1 |
运算符优先级
下表按降序优先级显示 Erlang 运算符的运算符优先级及其结合性。运算符优先级和结合性用于确定无括号表达式中的求值顺序。
运算符 | 结合性 |
---|---|
: | |
# | |
bnot, not | |
/, *, div, rem, band, and | 左结合 |
+,-, bor, bxor, or, xor | 左结合 |
==,/=,=<,<,>=,> |
Erlang - 循环
Erlang 是一种函数式编程语言,关于所有函数式编程语言需要注意的是,它们不提供任何循环结构。相反,函数式编程依赖于称为递归的概念。
while 语句的实现
由于 Erlang 中没有直接的 while 语句,因此必须使用 Erlang 中可用的递归技术来执行 while 语句的实现。
我们将尝试遵循与其他编程语言中相同的 while 循环实现方式。以下是将遵循的通用流程。
让我们来看一个如何使用递归在 Erlang 中实现 **while** 循环的示例。
示例
-module(helloworld). -export([while/1,while/2, start/0]). while(L) -> while(L,0). while([], Acc) -> Acc; while([_|T], Acc) -> io:fwrite("~w~n",[Acc]), while(T,Acc+1). start() -> X = [1,2,3,4], while(X).
关于上述程序,需要注意以下几点:
定义一个名为 while 的递归函数,该函数将模拟 while 循环的实现。
例如,将变量 X 中定义的值列表作为输入传递给 while 函数。
while 函数获取每个列表值并将中间值存储在变量“Acc”中。
然后针对列表中的每个值递归调用 while 循环。
上述代码的输出将是:
输出
0 1 2 3
for 语句
由于 Erlang 中没有直接的 **for** 语句,因此必须使用 Erlang 中可用的递归技术来执行 **for** 语句的实现。
我们将尝试遵循与其他编程语言中相同的 **for** 循环实现方式。以下是应遵守的通用流程。
让我们来看一个如何使用递归在 Erlang 中实现 **for** 循环的示例。
示例
-module(helloworld). -export([for/2,start/0]). for(0,_) -> []; for(N,Term) when N > 0 -> io:fwrite("Hello~n"), [Term|for(N-1,Term)]. start() -> for(5,1).
关于上述程序,需要注意以下几点:
我们正在定义一个递归函数,该函数将模拟 **for 循环** 的实现。
我们在“for”函数中使用保护条件来确保 N 或限制的值为正值。
我们通过在每次递归中减少 N 的值来递归调用 for 函数。
上述代码的输出将是:
输出
Hello Hello Hello Hello Hello
Erlang - 决策
决策结构要求程序员指定一个或多个条件供程序评估或测试,以及如果确定条件为 **true** 则要执行的语句,以及可选地,如果确定条件为 **false** 则要执行的其他语句。
以下是大多数编程语言中常见的典型决策结构的通用形式:
Erlang 编程语言提供以下类型的决策语句。
序号 | 语句和描述 |
---|---|
1 |
**if 语句** 由一个布尔表达式后跟一个或多个语句组成。 |
2 |
**if** 表达式也允许同时评估多个表达式。 |
3 |
您可以在另一个 **if** 或 **else if** 语句中使用一个 **if** 或 **else if** 语句。 |
4 |
它可以根据 case 语句的输出执行表达式。 |
Erlang - 函数
Erlang 被称为函数式编程语言,因此您期望看到很多关于 Erlang 中函数如何工作的重点。本章介绍了在 Erlang 中可以使用函数执行的所有操作。
定义函数
函数声明的语法如下:
语法
FunctionName(Pattern1… PatternN) -> Body;
其中:
**FunctionName** - 函数名是一个原子。
**Pattern1…PatternN** - 每个参数都是一个模式。参数的数量 N 是函数的元数。函数由模块名、函数名和元数唯一定义。也就是说,具有相同名称和在同一模块中但具有不同元数的两个函数是两个不同的函数。
**Body** - 子句体由逗号 (,) 分隔的一系列表达式组成。
以下程序是函数使用的简单示例:
示例
-module(helloworld). -export([add/2,start/0]). add(X,Y) -> Z = X+Y, io:fwrite("~w~n",[Z]). start() -> add(5,6).
关于上述程序,需要注意以下几点:
我们正在定义两个函数,一个是名为 **add** 的函数,它接受 2 个参数,另一个是 **start** 函数。
这两个函数都是使用 export 函数定义的。如果我们不这样做,我们将无法使用该函数。
一个函数可以在另一个函数内部调用。在这里,我们从 start 函数调用 add 函数。
上述程序的输出将是:
输出
11
匿名函数
匿名函数是一个没有与其关联名称的函数。Erlang 具有定义匿名函数的功能。以下程序是匿名函数的示例。
示例
-module(helloworld). -export([start/0]). start() -> Fn = fun() -> io:fwrite("Anonymous Function") end, Fn().
关于上述示例,需要注意以下几点:
匿名函数是用 **fun()** 关键字定义的。
函数被分配给名为 Fn 的变量。
函数通过变量名调用。
上述程序的输出将是:
输出
Anonymous Function
具有多个参数的函数
Erlang 函数可以定义为零个或多个参数。函数重载也是可能的,您可以多次定义同名函数,只要它们具有不同的参数数量即可。
在下面的示例中,函数 demo 为每个函数定义都定义了多个参数。
示例
-module(helloworld). -export([add/2,add/3,start/0]). add(X,Y) -> Z = X+Y, io:fwrite("~w~n",[Z]). add(X,Y,Z) -> A = X+Y+Z, io:fwrite("~w~n",[A]). start() -> add(5,6), add(5,6,6).
在上面的程序中,我们两次定义了 add 函数。但是,第一个 add 函数的定义接受两个参数,第二个接受三个参数。
上述程序的输出将是:
输出
11 17
带有保护条件序列的函数
Erlang 中的函数也具有保护条件序列的功能。这些只不过是只有在计算结果为 true 时才会导致函数运行的表达式。
带有保护条件序列的函数的语法显示在以下程序中。
语法
FunctionName(Pattern1… PatternN) [when GuardSeq1]-> Body;
其中:
**FunctionName** - 函数名是一个原子。
**Pattern1…PatternN** - 每个参数都是一个模式。参数的数量 N 是函数的元数。函数由模块名、函数名和元数唯一定义。也就是说,具有相同名称和在同一模块中但具有不同元数的两个函数是两个不同的函数。
**Body** - 子句体由逗号 (,) 分隔的一系列表达式组成。
**GuardSeq1** - 这是在调用函数时将计算的表达式。
以下程序是使用带有保护条件序列的函数的简单示例。
示例
-module(helloworld). -export([add/1,start/0]). add(X) when X>3 -> io:fwrite("~w~n",[X]). start() -> add(4).
上述程序的输出为:
输出
4
如果 add 函数被调用为 **add(3)**,则程序将导致错误。
Erlang - 模块
模块是一组在单个文件中、使用单个名称重新组合的函数。此外,Erlang 中的所有函数都必须在模块中定义。
大多数基本功能(如算术、逻辑和布尔运算符)已经可用,因为在运行程序时会加载默认模块。您将使用的模块中定义的每个其他函数都需要使用 **Module:Function** (Arguments) 的形式调用。
定义模块
使用模块,您可以声明两种东西:函数和属性。属性是描述模块本身的元数据,例如其名称、应该对外部世界可见的函数、代码的作者等等。这种元数据很有用,因为它向编译器提供了有关如何执行其工作的提示,而且因为它允许人们在无需查阅源代码的情况下从编译代码中检索有用的信息。
函数声明的语法如下:
语法
-module(modulename)
其中,**modulename** 是模块的名称。这必须是模块中代码的第一行。
以下程序显示名为 **helloworld** 的模块的示例。
示例
-module(helloworld). -export([start/0]). start() -> io:fwrite("Hello World").
上述程序的输出为:
输出
Hello World
模块属性
模块属性定义了模块的特定属性。模块属性由标签和值组成。
属性的通用语法如下:
语法
-Tag(Value)
属性使用方法的示例如下程序所示:
示例
-module(helloworld). -author("TutorialPoint"). -version("1.0"). -export([start/0]). start() -> io:fwrite("Hello World").
上述程序定义了两个自定义属性,名为 author 和 version,分别包含程序作者和程序版本号。
上述程序的输出为:
输出
Hello World
预构建属性
Erlang 有一些可以附加到模块的预构建属性。让我们来看看它们。
导出
exports 属性将采用函数和元数列表,以便其他模块使用。它将定义模块接口。我们之前的所有示例中都已看到这一点。
语法
export([FunctionName1/FunctionArity1,.,FunctionNameN/FunctionArityN])
其中:
FunctionName - 这是程序中函数的名称。
FunctionArity - 这是与函数关联的参数数量。
示例
-module(helloworld). -author("TutorialPoint"). -version("1.0"). -export([start/0]). start() -> io:fwrite("Hello World").
上述程序的输出将是:
输出
Hello World
导入
import 属性用于从另一个模块导入函数,以便将其用作局部函数。
语法
-import (modulename , [functionname/parameter]).
其中:
**Modulename** - 这是需要导入的模块的名称。
functionname/parameter - 需要导入的模块中的函数。
示例
-module(helloworld). -import(io,[fwrite/1]). -export([start/0]). start() -> fwrite("Hello, world!\n").
在上面的代码中,我们使用 import 关键字导入库“io”以及特定的 fwrite 函数。因此,现在每当我们调用 fwrite 函数时,就不必在任何地方都提及 io 模块名称。
上述程序的输出将是:
输出
Hello, world!
Erlang - 递归
递归是 Erlang 的重要组成部分。首先让我们看看如何通过实现阶乘程序来实现简单的递归。
示例
-module(helloworld). -export([fac/1,start/0]). fac(N) when N == 0 -> 1; fac(N) when N > 0 -> N*fac(N-1). start() -> X = fac(4), io:fwrite("~w",[X]).
关于上述程序,需要注意以下几点:
我们首先定义一个名为 fac(N) 的函数。
我们可以通过递归调用 fac(N) 来定义递归函数。
上述程序的输出为:
输出
24
递归的实用方法
在本节中,我们将详细了解不同类型的递归及其在 Erlang 中的用法。
长度递归
通过一个简单的示例可以看出递归的更实用的方法,该示例用于确定列表的长度。列表可以包含多个值,例如 [1,2,3,4]。让我们使用递归来了解如何获取列表的长度。
示例
-module(helloworld). -export([len/1,start/0]). len([]) -> 0; len([_|T]) -> 1 + len(T). start() -> X = [1,2,3,4], Y = len(X), io:fwrite("~w",[Y]).
关于上述程序,需要注意以下几点:
第一个函数 len([]) 用于列表为空时的特殊情况。
[H|T] 模式与一个或多个元素的列表匹配,长度为一的列表将定义为 [X|[]],长度为二的列表将定义为 [X|[Y|[]]]。请注意,第二个元素本身就是一个列表。这意味着我们只需要计算第一个元素,并且函数可以自身调用第二个元素。列表中的每个值都算作长度为 1。
上述程序的输出将是:
输出
4
尾递归
为了理解尾递归的工作原理,让我们了解上一节中以下代码的工作原理。
语法
len([]) -> 0; len([_|T]) -> 1 + len(T).
1 + len(Rest) 的答案需要找到 len(Rest) 的答案。然后,len(Rest) 函数本身需要找到另一个函数调用的结果。加法将一直堆叠到找到最后一个为止,只有这样才能计算最终结果。
尾递归旨在通过在发生时减少操作来消除这种操作堆叠。
为了实现这一点,我们需要在函数中将一个额外的临时变量作为参数。上述临时变量有时称为累加器,它充当一个存储计算结果的地方,这些结果会在发生时限制我们调用的增长。
让我们来看一个尾递归的示例:
示例
-module(helloworld). -export([tail_len/1,tail_len/2,start/0]). tail_len(L) -> tail_len(L,0). tail_len([], Acc) -> Acc; tail_len([_|T], Acc) -> tail_len(T,Acc+1). start() -> X = [1,2,3,4], Y = tail_len(X), io:fwrite("~w",[Y]).
上述程序的输出为:
输出
4
复制
让我们来看一个递归的例子。这次让我们编写一个函数,该函数以整数作为其第一个参数,然后以任何其他项作为其第二个参数。然后,它将根据整数创建多个项的列表。
让我们看看一个这样的例子:
-module(helloworld). -export([duplicate/2,start/0]). duplicate(0,_) -> []; duplicate(N,Term) when N > 0 -> io:fwrite("~w,~n",[Term]), [Term|duplicate(N-1,Term)]. start() -> duplicate(5,1).
上述程序的输出将是:
输出
1, 1, 1, 1, 1,
列表反转
在 Erlang 中使用递归没有限制。现在让我们快速了解一下如何使用递归反转列表的元素。可以使用以下程序来完成此操作。
示例
-module(helloworld). -export([tail_reverse/2,start/0]). tail_reverse(L) -> tail_reverse(L,[]). tail_reverse([],Acc) -> Acc; tail_reverse([H|T],Acc) -> tail_reverse(T, [H|Acc]). start() -> X = [1,2,3,4], Y = tail_reverse(X), io:fwrite("~w",[Y]).
上述程序的输出将是:
输出
[4,3,2,1]
关于上述程序,需要注意以下几点:
我们再次使用临时变量的概念将列表的每个元素存储在一个名为 Acc 的变量中。
然后我们递归调用 tail_reverse,但这次我们确保最后一个元素首先放入新列表中。
然后我们递归地为列表中的每个元素调用 tail_reverse。
Erlang - 数字
在 Erlang 中,有两种类型的数字文字,分别是整数和浮点数。以下是展示如何在 Erlang 中使用整数和浮点数的一些示例。
整数 - 下面的程序显示了如何将数字数据类型用作整数。此程序显示了两个整数的加法。
示例
-module(helloworld). -export([start/0]). start() -> io:fwrite("~w",[1+1]).
上述程序的输出如下:
输出
2
浮点数 - 下面的程序显示了如何将数字数据类型用作浮点数。此程序显示了两个整数的加法。
示例
-module(helloworld). -export([start/0]). start() -> io:fwrite("~w",[1.1+1.2]).
上述程序的输出如下:
输出
2.3
显示浮点数和指数数
使用 fwrite 方法将值输出到控制台时,有一些可用的格式化参数可用于将数字输出为浮点数或指数数。让我们看看如何实现这一点。
示例
-module(helloworld). -export([start/0]). start() -> io:fwrite("~f~n",[1.1+1.2]), io:fwrite("~e~n",[1.1+1.2]).
上述程序的输出如下:
输出
2.300000 2.30000e+0
关于上述程序,需要注意以下几点:
当指定 ~f 选项时,这意味着参数是一个浮点数,写为 [-]ddd.ddd,其中精度是小数点后的位数。默认精度为 6。
当指定 ~e 选项时,这意味着参数是一个浮点数,写为 [-]d.ddde+-ddd,其中精度是写入的位数。默认精度为 6。
数字的数学函数
Erlang 为数字提供了以下数学函数。请注意,Erlang 的所有数学函数都存在于 math 库中。因此,以下所有示例都将使用 import 语句导入 math 库中的所有方法。
序号 | 数学函数和描述 |
---|---|
1 |
此方法返回指定值的正弦。 |
2 |
此方法返回指定值的余弦。 |
3 |
此方法返回指定值的正切。 |
4 |
此方法返回指定值的反正弦。 |
5 |
此方法返回指定值的反余弦。 |
6 |
此方法返回指定值的反正切。 |
7 |
exp
此方法返回指定值的指数。 |
8 |
此方法返回指定值的对数。 |
9 |
此方法返回指定数字的绝对值。 |
10 |
此方法将数字转换为浮点值。 |
11 |
此方法检查数字是否为浮点值。 |
12 |
此方法检查数字是否为整数值。 |
Erlang - 字符串
字符串文字在 Erlang 中通过将字符串文本括在引号中来构造。Erlang 中的字符串需要使用双引号来构造,例如“Hello World”。
以下是 Erlang 中字符串用法的示例:
示例
-module(helloworld). -export([start/0]). start() -> Str1 = "This is a string", io:fwrite("~p~n",[Str1]).
上面的示例创建一个名为 Str1 的字符串变量。字符串“This is a string”被赋值给该变量并相应地显示。
上述程序的输出将是:
输出
“This is a string”
接下来,我们将讨论可用于字符串的各种操作。请注意,对于字符串操作,您还需要包含字符串库。
序号 | 字符串方法和描述 |
---|---|
1 |
此方法返回特定字符串的长度。 |
2 |
此方法返回一个布尔值,表示一个字符串是否等于另一个字符串。 |
3 |
此方法连接两个字符串并返回连接后的字符串。 |
4 |
此方法返回字符串中字符的索引位置。 |
5 |
此方法返回字符串中子字符串的索引位置。 |
6 |
此方法根据起始位置和从起始位置开始的字符数返回原始字符串中的子字符串。 |
7 |
此方法根据起始位置和从起始位置开始的字符数返回原始字符串中的子字符串。 |
带尾随字符的 left
此方法根据字符数返回字符串左侧的子字符串。但如果数字大于字符串的长度,则可以选择包含尾随字符。
语法
left(str1,number,$character)
参数
str1 - 这是需要从中提取子字符串的字符串。
Number - 这是子字符串中需要存在的字符数。
$Character - 要作为尾随字符包含的字符。
返回值
根据字符串的左侧和数字返回原始字符串中的子字符串。
例如
-module(helloworld). -import(string,[left/3]). -export([start/0]). start() -> Str1 = "hello", Str2 = left(Str1,10,$.), io:fwrite("~p~n",[Str2]).
输出
运行上述程序时,我们将得到以下结果。
"hello....."
right
此方法根据字符数返回字符串右侧的子字符串。
语法
right(str1,number)
参数
str1 - 这是需要从中提取子字符串的字符串。
Number - 这是子字符串中需要存在的字符数。
返回值
根据字符串的右侧和数字返回原始字符串中的子字符串。
例如
-module(helloworld). -import(string,[right/2]). -export([start/0]). start() -> Str1 = "hello World", Str2 = right(Str1,2), io:fwrite("~p~n",[Str2]).
输出
运行上述程序时,我们将得到以下结果。
“ld”
带尾随字符的 right
此方法根据字符数返回字符串右侧的子字符串。但如果数字大于字符串的长度,则可以选择包含尾随字符。
语法
right(str1,number,$character)
参数
str1 - 这是需要从中提取子字符串的字符串。
Number - 这是子字符串中需要存在的字符数。
$Character - 要作为尾随字符包含的字符。
返回值
根据字符串的右侧和数字返回原始字符串中的子字符串。
例如
-module(helloworld). -import(string,[right/3]). -export([start/0]). start() -> Str1 = "hello", Str2 = right(Str1,10,$.), io:fwrite("~p~n",[Str2]).
输出
运行上述程序时,我们将得到以下结果。
".....hello"
to_lower
此方法返回小写字符串。
语法
to_lower(str1)
参数
str1 - 这是需要转换为小写的字符串。
返回值
返回小写字符串。
例如
-module(helloworld). -import(string,[to_lower/1]). -export([start/0]). start() -> Str1 = "HELLO WORLD", Str2 = to_lower(Str1), io:fwrite("~p~n",[Str2]).
输出
运行上述程序时,我们将得到以下结果。
"hello world"
to_upper
此方法返回大写字符串。
语法
to_upper(str1)
参数
str1 - 这是需要转换为大写的字符串。
返回值 - 返回大写字符串。
例如
-module(helloworld). -import(string,[to_upper/1]). -export([start/0]). start() -> Str1 = "hello world", Str2 = to_upper(Str1), io:fwrite("~p~n",[Str2]).
输出
运行上述程序时,我们将得到以下结果。
"HELLO WORLD"
sub_string
返回字符串的子字符串,从 Start 位置开始到字符串的末尾,或到 Stop 位置(包括 Stop 位置)。
语法
sub_string(str1,start,stop)
参数
str1 - 这是需要从中返回子字符串的字符串。
start - 这是子字符串的起始位置
stop - 这是子字符串的停止位置
返回值
返回字符串的子字符串,从 Start 位置开始到字符串的末尾,或到 Stop 位置(包括 Stop 位置)。
例如
-module(helloworld). -import(string,[sub_string/3]). -export([start/0]). start() -> Str1 = "hello world", Str2 = sub_string(Str1,1,5), io:fwrite("~p~n",[Str2]).
输出
运行上述程序时,我们将得到以下结果。
"hello"
Erlang - 列表
列表是一种用于存储数据项集合的结构。在 Erlang 中,列表通过将值括在方括号中来创建。
以下是在 Erlang 中创建数字列表的一个简单示例。
示例
-module(helloworld). -export([start/0]). start() -> Lst1 = [1,2,3], io:fwrite("~w~n",[Lst1]).
上述示例的输出将是:
输出
[1 2 3]
现在让我们讨论可用于列表的各种方法。请注意,需要导入列表库才能使这些方法起作用。
序号 | 方法和描述 |
---|---|
1 |
如果 Pred(Elem) 对 List 中的所有元素 Elem 返回 true,则返回 true,否则返回 false。 |
2 |
如果 List 中至少有一个元素 Elem 使得 Pred(Elem) 返回 true,则返回 true。 |
3 |
返回一个新的列表 List3,该列表由 List1 的元素后跟 List2 的元素组成。 |
4 |
从列表中删除一个元素并返回一个新的列表。 |
5 |
删除 List 的最后一个元素。 |
6 |
返回一个包含 N 个 Elem 项的列表。 |
7 |
返回列表的最后一个元素。 |
8 |
返回列表中具有最大值的元素。 |
9 |
检查列表中是否存在某个元素。 |
10 |
返回列表中具有最小值的元素。 |
11 |
返回由合并 ListOfLists 的所有子列表形成的排序列表。 |
12 |
返回 List 的第 N 个元素。 |
13 |
返回 List 的第 N 个尾部。 |
14 |
反转元素列表。 |
15 |
对元素列表进行排序。 |
16 |
返回元素的子列表。 |
17 |
返回列表中元素的总和。 |
Erlang - 文件 I/O
Erlang 在处理 I/O 时提供了许多方法。它拥有更简单的类来为文件提供以下功能:
- 读取文件
- 写入文件
- 查看文件是文件还是目录
Erlang 中的文件操作方法
让我们探索 Erlang 提供的一些文件操作。出于这些示例的目的,我们将假设存在一个名为 **NewFile.txt** 的文件,其中包含以下几行文本:
示例1
示例2
示例3
此文件将用于以下示例中的读写操作。
一次读取文件内容的一行
文件的一般操作是通过使用文件库中提供的方法来执行的。对于文件的读取,我们需要首先使用打开操作,然后使用文件库中提供的读取操作。以下是这两种方法的语法:
语法
- 打开文件 – Open(File,Mode)
- 读取文件 – read(FileHandler,NumberofBytes)
参数
**File** – 需要打开的文件位置。
**Mode** – 需要打开文件的模式。
以下是可用的模式:
**Read** – 打开文件以进行读取(文件必须存在)。
**Write** – 打开文件以进行写入。如果文件不存在,则创建文件。如果文件存在,并且 write 没有与 read 组合使用,则文件将被截断。
**Append** – 打开文件以进行写入,如果文件不存在,则创建文件。对以追加模式打开的文件的每次写入操作都将发生在文件的末尾。
**Exclusive** – 打开文件以进行写入时,如果文件不存在,则创建文件。如果文件存在,open 将返回 {error, exist}。
**FileHandler** – 文件句柄。使用 **file:open** 操作时将返回此句柄。
**NumberofByte** – 需要从文件中读取的字节数。
返回值
**Open(File,Mode)** – 如果操作成功,则返回文件句柄。
**read(FileHandler,NumberofBytes)** – 从文件中返回请求的读取信息。
例如
-module(helloworld). -export([start/0]). start() -> {ok, File} = file:open("Newfile.txt",[read]), Txt = file:read(File,1024 * 1024), io:fwrite("~p~n",[Txt]).
**输出** – 运行上述程序时,我们将得到以下结果。
Example1
现在让我们讨论一些可用于文件操作的其他方法:
序号 | 方法和描述 |
---|---|
1 |
允许一次读取文件的所有内容。 |
2 |
用于将内容写入文件。 |
3 |
用于复制现有文件。 |
4 |
此方法用于删除现有文件。 |
5 |
此方法用于列出特定目录的内容。 |
6 |
此方法用于创建新目录。 |
7 |
此方法用于重命名现有文件。 |
8 |
此方法用于确定文件的大小。 |
9 |
此方法用于确定文件是否确实是文件。 |
10 |
此方法用于确定目录是否确实是目录。 |
Erlang - 原子
原子是一个文字,一个带有名称的常量。如果原子不以小写字母开头,或者包含除字母数字字符、下划线 (_) 或 @ 之外的其他字符,则应将其括在单引号 ('') 中。
以下程序是原子如何在 Erlang 中使用的示例。此程序分别声明了 3 个原子,分别是 atom1、atom_1 和 'atom 1'。因此您可以看到声明原子的不同方式。
示例
-module(helloworld). -export([start/0]). start() -> io:fwrite(atom1), io:fwrite("~n"), io:fwrite(atom_1), io:fwrite("~n"), io:fwrite('atom 1'), io:fwrite("~n").
上述程序的输出如下:
输出
atom1 atom_1 atom 1
让我们看看 Erlang 中一些可用于处理原子的方法。
序号 | 方法和描述 |
---|---|
1 |
此方法用于确定术语是否确实是原子。 |
2 |
此方法用于将原子转换为列表。 |
3 |
此方法用于将列表项转换为原子。 |
4 |
此方法用于将原子转换为二进制值。 |
5 |
此方法用于将二进制值转换为原子值。 |
Erlang - 映射
映射是一种复合数据类型,具有可变数量的键值关联。映射中的每个键值关联称为关联对。对的键和值部分称为元素。关联对的数量称为映射的大小。
以下程序显示了如何使用映射数据类型的示例。
在这里,我们定义了一个具有 2 个映射的映射 M1。**map_size** 是 Erlang 中定义的内置函数,可用于确定映射的大小。
示例
-module(helloworld). -export([start/0]). start() -> M1 = #{name=>john,age=>25}, io:fwrite("~w",[map_size(M1)]).
上述程序的输出如下。
输出
2
映射的一些其他方法如下所示。
序号 | 方法和描述 |
---|---|
1 |
此方法用于根据列表生成映射。 |
2 |
此方法用于查找映射中是否存在特定键。 |
3 |
此方法用于获取映射中特定键的值。 |
4 |
此方法用于确定特定键是否在映射中定义为键。 |
5 |
此方法用于返回映射中的所有键。 |
6 |
此方法用于合并两个映射。 |
7 |
此方法用于向映射添加键值对。 |
8 |
此方法用于返回映射中的所有值。 |
9 |
此方法用于从映射中移除键值。 |
Erlang - 元组
元组是一种复合数据类型,具有固定数量的项。元组中的每一项称为元素。元素的数量称为元组的大小。
以下程序显示了如何使用元组数据类型的示例。
在这里,我们定义了一个具有 3 个项的 **元组 P**。**tuple_size** 是 Erlang 中定义的内置函数,可用于确定元组的大小。
示例
-module(helloworld). -export([start/0]). start() -> P = {john,24,{june,25}} , io:fwrite("~w",[tuple_size(P)]).
上述程序的输出如下。
输出
3
让我们看看元组中还有一些可用的操作。
序号 | 方法和描述 |
---|---|
1 |
此方法用于确定提供的术语是否确实是元组。 |
2 |
此方法用于将列表转换为元组。 |
3 |
此方法用于将元组转换为列表。 |
Erlang - 记录
Erlang 具有创建记录的额外功能。这些记录由字段组成。例如,您可以定义一个个人记录,它有两个字段,一个是 id 字段,另一个是 name 字段。在 Erlang 中,您可以创建此记录的各种实例来定义具有各种名称和 ID 的多个人。
让我们探索如何使用记录。
创建记录
使用记录标识符创建记录。在此记录标识符中,您指定构成记录的各个字段。下面给出一般语法和示例。
语法
record(recordname , {Field1,Field2 ..Fieldn})
参数
**recordname** – 这是赋予记录的名称。
**Field1,Field2 ..Fieldn** – 这些是构成记录的各个字段的列表。
返回值
无
例如
-module(helloworld). -export([start/0]). -record(person, {name = "", id}). start() -> P = #person{name="John",id = 1}.
以上示例显示了具有 2 个字段的记录的定义,一个是 id,另一个是 name。此外,记录的构造方式如下:
语法
#recordname {fieldName1 = value1, fieldName2 = value2 .. fieldNameN = valueN}
在定义记录的实例时,您将值分配给各个字段。
访问记录的值
要访问特定记录的字段和值,应使用以下语法。
语法
#recordname.Fieldname
参数
**recordname** – 这是赋予记录的名称。
**Fieldname** – 这是需要访问的字段的名称。
返回值
分配给字段的值。
例如
-module(helloworld). -export([start/0]). -record(person, {name = "", id}). start() -> P = #person{name = "John",id = 1}, io:fwrite("~p~n",[P#person.id]), io:fwrite("~p~n",[P#person.name]).
输出
上述程序的输出如下。
1 “John”
更新记录的值
记录值的更新是通过将值更改为特定字段,然后将记录分配给新的变量名来完成的。下面给出一般语法和示例。
语法
#recordname.Fieldname = newvalue
参数
**recordname** – 这是赋予记录的名称。
**Fieldname** – 这是需要访问的字段的名称。
**newvalue** – 这是需要分配给字段的新值。
返回值
具有分配给字段的新值的新记录。
例如
-module(helloworld). -export([start/0]). -record(person, {name = "", id}). start() -> P = #person{name = "John",id = 1}, P1 = P#person{name = "Dan"}, io:fwrite("~p~n",[P1#person.id]), io:fwrite("~p~n",[P1#person.name]).
输出
上述程序的输出如下:
1 “Dan”
嵌套记录
Erlang 还具有嵌套记录的功能。以下示例显示了如何创建这些嵌套记录。
例如
-module(helloworld). -export([start/0]). -record(person, {name = "", address}). -record(employee, {person, id}). start() -> P = #employee{person = #person{name = "John",address = "A"},id = 1}, io:fwrite("~p~n",[P#employee.id]).
在上述示例中,需要注意以下几点:
我们首先创建一个人的记录,其字段值为姓名和地址。
然后我们定义一个员工记录,它将人作为字段,还有一个名为 id 的附加字段。
输出
上述程序的输出如下。
1
Erlang - 异常
任何编程语言都需要异常处理来处理运行时错误,以便维护应用程序的正常流程。异常通常会中断应用程序的正常流程,这就是为什么我们需要在应用程序中使用异常处理的原因。
通常,当 Erlang 中发生异常或错误时,将显示以下消息。
{"init terminating in do_boot", {undef,[{helloworld,start,[],[]}, {init,start_it,1,[]},{init,start_em,1,[]}]}}
崩溃转储将写入 -
erl_crash.dump init terminating in do_boot ()
在 Erlang 中,有三种类型的异常 -
错误 (Error) - 调用 erlang:error(Reason) 将终止当前进程的执行,并在捕获时包含最后调用的函数及其参数的堆栈跟踪。这些是导致上述运行时错误的异常类型。
退出 (Exit) - 有两种类型的退出:'内部' 退出和 '外部' 退出。内部退出是通过调用函数 exit/1 触发的,并使当前进程停止执行。外部退出是使用 exit/2 调用的,与 Erlang 并发方面的多个进程有关。
抛出 (Throw) - 抛出是一种异常类型,用于程序员可以预期处理的情况。与退出和错误相比,它们并没有真正带有任何“崩溃进程!”的意图,而是控制流程。由于您在预期程序员处理抛出时使用它们,因此通常最好使用它们记录模块中的使用情况。
try ... catch 是一种评估表达式的方 式,同时允许您处理成功的情况以及遇到的错误。
try catch 表达式的通用语法如下。
语法
try Expression of SuccessfulPattern1 [Guards] -> Expression1; SuccessfulPattern2 [Guards] -> Expression2 catch TypeOfError:ExceptionPattern1 -> Expression3; TypeOfError:ExceptionPattern2 -> Expression4 end
try 和 of 之间的表达式被称为受保护的。这意味着在该调用中发生的任何类型的异常都将被捕获。try ... of 和 catch 之间的模式和表达式与 case ... of 的行为完全相同。
最后是 catch 部分 – 在这里,您可以将 TypeOfError 替换为 error、throw 或 exit,分别对应我们在本章中看到的每种类型。如果未提供类型,则假定为 throw。
以下是 Erlang 中的一些错误和错误原因 -
错误 | 错误类型 |
---|---|
badarg | 参数错误。参数的数据类型错误,或者格式错误。 |
badarith | 算术表达式中的参数错误。 |
{badmatch,V} | 匹配表达式的评估失败。值 V 不匹配。 |
function_clause | 在评估函数调用时找不到匹配的函数子句。 |
{case_clause,V} | 在评估 case 表达式时找不到匹配的分支。值 V 不匹配。 |
if_clause | 在评估 if 表达式时找不到 true 分支。 |
{try_clause,V} | 在评估 try 表达式的 of 部分时找不到匹配的分支。值 V 不匹配。 |
undef | 在评估函数调用时找不到函数。 |
{badfun,F} | fun F 有问题。 |
{badarity,F} | fun 应用于错误数量的参数。F 描述了 fun 和参数。 |
timeout_value | receive..after 表达式中的超时值计算结果不是整数或无穷大。 |
noproc | 尝试链接到不存在的进程。 |
以下是这些异常如何使用以及如何操作的示例。
第一个函数生成所有可能的异常类型。
然后我们编写一个包装器函数,在 try...catch 表达式中调用 generate_exception。
示例
-module(helloworld). -compile(export_all). generate_exception(1) -> a; generate_exception(2) -> throw(a); generate_exception(3) -> exit(a); generate_exception(4) -> {'EXIT', a}; generate_exception(5) -> erlang:error(a). demo1() -> [catcher(I) || I <- [1,2,3,4,5]]. catcher(N) -> try generate_exception(N) of Val -> {N, normal, Val} catch throw:X -> {N, caught, thrown, X}; exit:X -> {N, caught, exited, X}; error:X -> {N, caught, error, X} end. demo2() -> [{I, (catch generate_exception(I))} || I <- [1,2,3,4,5]]. demo3() -> try generate_exception(5) catch error:X -> {X, erlang:get_stacktrace()} end. lookup(N) -> case(N) of 1 -> {'EXIT', a}; 2 -> exit(a) end.
如果我们运行程序 helloworld:demo(),我们将获得以下输出 -
输出
[{1,normal,a}, {2,caught,thrown,a}, {3,caught,exited,a}, {4,normal,{'EXIT',a}}, {5,caught,error,a}]
Erlang - 宏
宏通常用于内联代码替换。在 Erlang 中,宏通过以下语句定义。
- -define(Constant, Replacement).
- -define(Func(Var1, Var2,.., Var), Replacement).
以下是使用第一种语法的宏示例 -
示例
-module(helloworld). -export([start/0]). -define(a,1). start() -> io:fwrite("~w",[?a]).
从上面的程序中您可以看到,宏是通过使用“?”符号展开的。常量在原处被宏中定义的值替换。
上述程序的输出将是:
输出
1
使用函数类宏的示例如下 -
示例
-module(helloworld). -export([start/0]). -define(macro1(X,Y),{X+Y}). start() -> io:fwrite("~w",[?macro1(1,2)]).
上述程序的输出将是:
输出
{3}
以下附加语句可用于宏 -
undef(Macro) - 取消定义宏;此后您无法调用该宏。
ifdef(Macro) - 仅当已定义 Macro 时才评估以下行。
ifndef(Macro) - 仅当 Macro 未定义时才评估以下行。
else - 允许在 ifdef 或 ifndef 语句之后使用。如果条件为假,则评估 else 之后的语句。
endif - 标记 ifdef 或 ifndef 语句的结尾。
使用上述语句时,应按照以下程序中所示的方式正确使用。
-ifdef(<FlagName>). -define(...). -else. -define(...). -endif.
Erlang - 头文件
头文件就像任何其他编程语言中的包含文件一样。它对于将模块拆分为不同的文件然后在单独的程序中访问这些头文件很有用。为了查看头文件的实际作用,让我们看看我们前面关于记录的示例之一。
让我们首先创建一个名为 user.hrl 的文件并添加以下代码 -
-record(person, {name = "", id}).
现在在我们的主程序文件中,让我们添加以下代码 -
示例
-module(helloworld). -export([start/0]). -include("user.hrl"). start() -> P = #person{name = "John",id = 1}, io:fwrite("~p~n",[P#person.id]), io:fwrite("~p~n",[P#person.name]).
正如您从上面的程序中看到的,我们实际上只是包含 user.hrl 文件,它会自动插入 –record 代码。
如果您执行上述程序,您将获得以下输出。
输出
1 “John”
您也可以对宏执行相同的操作,您可以在头文件中定义宏并在主文件中引用它。让我们看一个这样的例子 -
让我们首先创建一个名为 user.hrl 的文件并添加以下代码 -
-define(macro1(X,Y),{X+Y}).
现在在我们的主程序文件中,让我们添加以下代码 -
示例
-module(helloworld). -export([start/0]). -include("user.hrl"). start() -> io:fwrite("~w",[?macro1(1,2)]).
如果您执行上述程序,您将获得以下输出 -
输出
{3}
Erlang - 预处理器
在编译 Erlang 模块之前,它会由 Erlang 预处理器自动处理。预处理器将展开源文件中可能存在的任何宏,并插入任何必要的包含文件。
通常,您不需要查看预处理器的输出,但在特殊情况下(例如,调试有故障的宏时),您可能希望保存预处理器的输出。要查看预处理模块 some_module.erl 的结果,请使用 OS shell 命令。
erlc -P some_module.erl
例如,假设我们有以下代码文件 -
示例
-module(helloworld). -export([start/0]). -include("user.hrl"). start() -> io:fwrite("~w",[?macro1(1,2)]).
如果我们从命令行执行以下命令 -
erlc –P helloworld.erl
将生成一个名为 helloworld.P 的文件。如果您打开此文件,您将找到以下内容,这就是预处理器将编译的内容。
-file("helloworld.erl", 1). -module(helloworld). -export([start/0]). -file("user.hrl", 1). -file("helloworld.erl", 3). start() -> io:fwrite("~w", [{1 + 2}]).
Erlang - 模式匹配
模式看起来与术语相同——它们可以是简单的文字,如原子和数字,也可以是复合的,如元组和列表,或者两者的混合。它们还可以包含变量,这些变量是开头为大写字母或下划线的字母数字字符串。一个特殊的“匿名变量” _(下划线)用于您不关心要匹配的值,并且不会使用它。
如果模式具有与要匹配的术语相同的“形状”,并且遇到的原子相同,则模式匹配成功。例如,以下匹配成功 -
- B = 1.
- 2 = 2.
- {ok, C} = {ok, 40}.
- [H|T] = [1, 2, 3,4].
请注意,在第四个示例中,管道(|)表示列表的首部和尾部,如术语中所述。另请注意,左侧应与右侧匹配,这是模式的正常情况。
以下模式匹配示例将失败。
- 1 = 2.
- {ok, A} = {failure, "Don't know the question"}.
- [H|T] = [].
在模式匹配运算符的情况下,失败会生成错误,并且进程退出。如何在错误中捕获和处理这一点将在错误中介绍。模式用于选择将执行哪个函数子句。
Erlang - 保护条件
保护条件是我们用来增强模式匹配功能的结构。使用保护条件,我们可以对模式中的变量执行简单的测试和比较。
保护语句的通用语法如下 -
function(parameter) when condition ->
其中:
Function(parameter) - 这是在保护条件中使用的函数声明。
Parameter - 通常,保护条件基于参数。
Condition - 应评估的条件,以查看是否应执行该函数。
指定保护条件时必须使用 when 语句。
让我们快速了解一下如何使用保护条件 -
示例
-module(helloworld). -export([display/1,start/0]). display(N) when N > 10 -> io:fwrite("greater then 10"); display(N) when N < 10 -> io:fwrite("Less than 10"). start() -> display(11).
关于上述示例,需要注意以下几点 -
显示函数与保护条件一起定义。第一个显示声明有一个保护条件,即参数 N 大于 10。因此,如果参数大于 10,则将调用该函数。
再次定义显示函数,但这次的保护条件是小于 10。通过这种方式,您可以多次定义相同的函数,每个函数都有一个单独的保护条件。
上述程序的输出如下:
输出
greater than 10
保护条件也可以用于 if else 和 case 语句。让我们看看如何在这些语句上执行保护操作。
‘if’ 语句的保护条件
保护条件也可以用于 if 语句,以便执行的语句序列基于保护条件。让我们看看如何实现这一点。
示例
-module(helloworld). -export([start/0]). start() -> N = 9, if N > 10 -> io:fwrite("N is greater than 10"); true -> io:fwrite("N is less than 10") end.
关于上述示例,需要注意以下几点 -
保护函数与 if 语句一起使用。如果保护函数评估结果为真,则显示语句“N 大于 10”。
如果保护函数评估结果为假,则显示语句“N 小于 10”。
上述程序的输出如下:
输出
N is less than 10
‘case’ 语句的保护条件
保护条件也可以用于 case 语句,以便执行的语句序列基于保护条件。让我们看看如何实现这一点。
示例
-module(helloworld). -export([start/0]). start() -> A = 9, case A of {A} when A>10 -> io:fwrite("The value of A is greater than 10"); _ -> io:fwrite("The value of A is less than 10") end.
关于上述示例,需要注意以下几点 -
保护函数与 case 语句一起使用。如果保护函数评估结果为真,则显示语句“A 的值大于 10”。
如果保护函数评估结果为其他任何值,则显示语句“A 的值小于 10”。
上述程序的输出如下:
输出
The value of A is less than 10
多个保护条件
还可以为函数指定多个保护条件。具有多个保护条件的保护语句的通用语法如下所示 -
function(parameter) when condition1 , condition1 , .. conditionN ->
其中:
Function(parameter) - 这是使用保护条件的函数声明。
Parameter - 通常,保护条件基于参数。
condition1, condition1, .. conditionN - 这些是应用于函数的多个保护条件。
指定保护条件时必须使用 when 语句。
让我们快速了解一下如何使用多个保护条件 -
示例
-module(helloworld). -export([display/1,start/0]). display(N) when N > 10 , is_integer(N) -> io:fwrite("greater then 10"); display(N) when N < 10 -> io:fwrite("Less than 10"). start() -> display(11).
关于上述示例,需要注意以下几点 -
你会注意到,对于第一个显示函数声明,除了 N>10 的条件外,还指定了 **is_integer** 条件。因此,只有当 N 的值是整数且大于 10 时,才会执行此函数。
上述程序的输出如下:
输出
Greater than 10
Erlang - BIFS (内置函数)
BIF 是内置于 Erlang 的函数。它们通常执行 Erlang 中无法编程完成的任务。例如,不可能将列表转换为元组,也无法查找当前时间和日期。要执行此类操作,我们调用 BIF。
让我们来看一个 BIF 的使用方法示例:
示例
-module(helloworld). -export([start/0]). start() -> io:fwrite("~p~n",[tuple_to_list({1,2,3})]), io:fwrite("~p~n",[time()]).
关于上述示例,需要注意以下几点 -
在第一个示例中,我们使用名为 **tuple_to_list** 的 BIF 将元组转换为列表。
在第二个 BIF 函数中,我们使用 **time 函数** 输出系统时间。
上述程序的输出如下:
输出
[1,2,3] {10,54,56}
让我们来看一下 Erlang 中可用的其他一些 BIF 函数。
序号 | BIF 函数及说明 |
---|---|
1 |
此方法返回当前系统日期。 |
2 |
此方法返回 Bitstring 中包含的字节数。 |
3 |
此方法返回元组中的第 N 个元素。 |
4 |
此方法返回特定数字的浮点值。 |
5 |
此方法将进程字典作为列表返回。 |
6 |
此方法用于在进程字典中放入 **键值对**。 |
7 |
此方法用于提供系统中的本地日期和时间。 |
8 |
返回一个列表,其中包含有关 Erlang 模拟器动态分配的内存的信息。 |
9 |
此方法返回元组 {MegaSecs, Secs, MicroSecs},它是自 1970 年 1 月 1 日 00:00 GMT 以来经过的时间。 |
10 |
返回本地节点上的所有端口列表。 |
11 |
返回与当前在本地节点上存在的所有进程对应的进程标识符列表。 |
12 |
根据协调世界时 (UTC) 返回当前日期和时间。 |
Erlang - 二进制
使用名为二进制的数据结构来存储大量原始数据。与列表或元组相比,二进制以更节省空间的方式存储数据,并且运行时系统针对二进制的高效输入和输出进行了优化。
二进制被写入和打印为整数或字符串序列,括在双小于和大于括号中。
以下是 Erlang 中二进制的示例:
示例
-module(helloworld). -export([start/0]). start() -> io:fwrite("~p~n",[<<5,10,20>>]), io:fwrite("~p~n",[<<"hello">>]).
运行上述程序时,我们将得到以下结果。
输出
<<5,10,20>> <<"hello">>
让我们来看一下 Erlang 中可用于处理二进制的函数:
序号 | 方法和描述 |
---|---|
1 |
此方法用于将现有列表转换为二进制列表。 |
2 |
此方法用于根据指定的索引位置拆分二进制列表。 |
3 |
此方法用于将项转换为二进制。 |
4 |
此方法用于检查比特串是否确实是二进制值。 |
5 |
此方法用于提取二进制字符串的一部分。 |
6 |
此方法用于将二进制值转换为浮点值。 |
7 |
此方法用于将二进制值转换为整数值。 |
8 |
此方法用于将二进制值转换为列表。 |
9 |
此方法用于将二进制值转换为原子。 |
Erlang - Fun (匿名函数)
Fun 用于在 Erlang 中定义匿名函数。匿名函数的一般语法如下:
语法
F = fun (Arg1, Arg2, ... ArgN) -> ... End
其中
**F** - 这是分配给匿名函数的变量名。
**Arg1, Arg2, ... ArgN** - 这些是传递给匿名函数的参数。
以下示例展示了如何使用匿名函数。
示例
-module(helloworld). -export([start/0]). start() -> A = fun() -> io:fwrite("Hello") end, A().
关于上述程序,需要注意以下几点:
匿名函数被赋值给变量 A。
通过变量 A() 调用匿名函数。
运行上述程序后,我们将得到以下结果。
“Hello”
另一个匿名函数示例如下,但这是使用参数的。
-module(helloworld). -export([start/0]). start() -> A = fun(X) -> io:fwrite("~p~n",[X]) end, A(5).
运行上述程序后,我们将得到以下结果。
输出
5
使用变量
匿名函数能够访问匿名函数作用域之外的变量。让我们来看一个例子:
示例
-module(helloworld). -export([start/0]). start() -> B = 6, A = fun(X) -> io:fwrite("~p~n",[X]), io:fwrite("~p~n",[B]) end, A(5).
关于上述程序,需要注意以下几点:
变量 B 在匿名函数的作用域之外。
匿名函数仍然可以访问在全局作用域中定义的变量。
运行上述程序后,我们将得到以下结果。
输出
5 6
函数中的函数
高阶函数的另一个最强大的方面是,你可以在一个函数中定义另一个函数。让我们来看一个如何实现这一点的例子。
示例
-module(helloworld). -export([start/0]). start() -> Adder = fun(X) -> fun(Y) -> io:fwrite("~p~n",[X + Y]) end end, A = Adder(6), A(10).
关于上述程序,需要注意以下几点:
Adder 是一个定义为 fun(X) 的高阶函数。
Adder 函数 fun(X) 引用另一个函数 fun(Y)。
运行上述程序后,我们将得到以下结果。
输出
16
Erlang - 进程
Erlang 中并发性的粒度是进程。进程是一个与其他进程并发运行且独立于其他进程的活动/任务。Erlang 中的这些进程与大多数人熟悉的进程和线程不同。Erlang 进程是轻量级的,在 (内存) 中与其他进程隔离运行,并由 Erlang 的虚拟机 (VM) 调度。进程的创建时间非常低,刚刚生成的进程的内存占用非常小,单个 Erlang VM 可以运行数百万个进程。
进程是借助 spawn 方法创建的。该方法的一般语法如下所示。
语法
spawn(Module, Name, Args)
参数
**Module** - 这是一个预定义的原子值,必须是 ?MODULE。
**Name** - 这是在定义进程时要调用的函数的名称。
**Args** - 这些是要发送到函数的参数。
返回值
返回新创建进程的进程 ID。
例如
以下程序显示了 spawn 方法的示例。
-module(helloworld). -export([start/0, call/2]). call(Arg1, Arg2) -> io:format("~p ~p~n", [Arg1, Arg2]). start() -> Pid = spawn(?MODULE, call, ["hello", "process"]), io:fwrite("~p",[Pid]).
关于上述程序,需要注意以下几点:
定义了一个名为 call 的函数,将用于创建进程。
spawn 方法使用参数 hello 和 process 调用 call 函数。
输出
运行上述程序后,我们将得到以下结果。
<0.29.0>"hello" "process"
现在让我们来看一下进程的其他可用函数。
序号 | 方法和描述 |
---|---|
1 |
此方法用于确定进程 ID 是否存在。 |
2 |
这称为 is_process_alive(Pid)。Pid 必须引用本地节点上的进程。 |
3 |
它将进程 ID 转换为列表。 |
4 |
返回一个列表,其中包含所有已注册进程的名称。 |
5 |
最常用的 BIF 之一,返回调用进程的 pid。 |
6 |
这用于在系统中注册进程。 |
7 |
它被称为 whereis(Name)。返回以该名称注册的进程的 pid。 |
8 |
这用于在系统中注销进程。 |
Erlang - 电子邮件
要使用 Erlang 发送电子邮件,你需要使用 **github** 上提供的软件包。github 链接为:https://github.com/Vagabond/gen_smtp
此链接包含一个 **smtp 实用程序**,可用于从 Erlang 应用程序发送电子邮件。请按照以下步骤操作,即可从 Erlang 发送电子邮件。
**步骤 1** - 从 **github 站点**下载 **erl 文件**。应将文件下载到你的 **helloworld.erl** 应用程序所在的目录。
**步骤 2** - 使用 **erlc 命令**编译以下列表中显示的所有 **与 smtp 相关的文件**。需要编译以下文件:
- smtp_util
- gen_smtp_client
- gen_smtp_server
- gen_smtp_server_session
- binstr
- gen_smtp_application
- socket
**步骤 3** - 可以编写以下代码来使用 smtp 发送电子邮件。
示例
-module(helloworld). -export([start/0]). start() -> gen_smtp_client:send({"[email protected]", ["[email protected]"], "Subject: testing"}, [{relay, "smtp.gmail.com"}, {ssl, true}, {username, "[email protected]"}, {password, "senderpassword"}]).
关于上述程序,需要注意以下几点:
上述 smtp 函数与 Google 提供的 smtp 服务器一起使用。
由于我们想使用安全的 smtp 发送,因此我们将 ssl 参数指定为 true。
你需要将中继指定为 **smtp.gmail.com**。
你需要提及具有发送电子邮件权限的用户名和密码。
配置上述所有设置并执行程序后,接收方将成功收到电子邮件。
Erlang - 数据库
Erlang 能够连接到传统的数据库,例如 SQL Server 和 Oracle。Erlang 有一个 **内置的 odbc 库**,可用于处理数据库。
数据库连接
在我们的示例中,我们将使用 Microsoft SQL Server。在连接到 Microsoft SQL Server 数据库之前,请确保检查以下要点。
你已创建数据库 TESTDB。
你已在 TESTDB 中创建表 EMPLOYEE。
此表具有字段 FIRST_NAME、LAST_NAME、AGE、SEX 和 INCOME。
已设置用户 ID“testuser”和密码“test123”以访问 TESTDB。
确保你已创建名为 **usersqlserver** 的 ODBC DSN,它创建到数据库的 ODBC 连接。
建立连接
要建立到数据库的连接,可以使用以下代码示例。
示例
-module(helloworld). -export([start/0]). start() -> odbc:start(), {ok, Ref} = odbc:connect("DSN = usersqlserver;UID = testuser;PWD = test123", []), io:fwrite("~p",[Ref]).
上述程序的输出如下:
输出
<0.33.0>
关于上述程序,需要注意以下几点:
odbc 库的 start 方法用于指示数据库操作的开始。
connect 方法需要 DSN、用户名和密码才能连接。
创建数据库表
连接到数据库后的下一步是在数据库中创建表。以下示例显示了如何使用 Erlang 在数据库中创建表。
示例
-module(helloworld). -export([start/0]). start() -> odbc:start(), {ok, Ref} = odbc:connect("DSN = usersqlserver; UID = testuser;PWD = test123, []), odbc:sql_query(Ref, "CREATE TABLE EMPLOYEE (FIRSTNAME char varying(20), LASTNAME char varying(20), AGE integer, SEX char(1), INCOME integer)")
如果你现在检查数据库,你将看到已创建名为 **EMPLOYEE** 的表。
将记录插入数据库
当你想将记录创建到数据库表中时,这是必需的。
以下示例将记录插入 employee 表中。如果表成功更新,则记录和语句将返回更新记录的值以及已更新的记录数。
示例
-module(helloworld). -export([start/0]). start() -> odbc:start(), {ok, Ref} = odbc:connect("DSN = usersqlserver; UID = testuser;PWD = test123", []), io:fwrite("~p",[odbc:sql_query(Ref, "INSERT INTO EMPLOYEE VALUES('Mac', 'Mohan', 20, 'M', 2000)")]).
上述程序的输出将是:
输出
{updated,1}
从数据库中提取记录
Erlang 还能够从数据库中提取记录。这是通过 **sql_query 方法**完成的。
以下程序显示了一个示例:
示例
-module(helloworld). -export([start/0]). start() -> odbc:start(), {ok, Ref} = odbc:connect("DSN = usersqlserver; UID = testuser;PWD = test123", []), io:fwrite("~p",[odbc:sql_query(Ref, "SELECT * FROM EMPLOYEE") ]).
上述程序的输出如下:
输出
{selected,["FIRSTNAME","LASTNAME","AGE","SEX","INCOME"], [{"Mac","Mohan",20,"M",2000}]}
因此,你可以看到上一节中的插入命令有效,并且选择命令返回了正确的数据。
基于参数从数据库中提取记录
Erlang 还能够根据某些筛选条件从数据库中提取记录。
示例如下:
示例
-module(helloworld). -export([start/0]). start() -> odbc:start(), {ok, Ref} = odbc:connect("DSN=usersqlserver; UID=testuser;PWD=test123", []), io:fwrite("~p",[ odbc:param_query(Ref, "SELECT * FROM EMPLOYEE WHERE SEX=?", [{{sql_char, 1}, ["M"]}])]).
上述程序的输出将是:
输出
{selected,["FIRSTNAME","LASTNAME","AGE","SEX","INCOME"], [{"Mac","Mohan",20,"M",2000}]}
更新数据库中的记录
Erlang 还能够更新数据库中的记录。
相同的示例如下:
示例
-module(helloworld). -export([start/0]). start() -> odbc:start(), {ok, Ref} = odbc:connect("DSN = usersqlserver; UID = testuser;PWD = test123", []), io:fwrite("~p",[ odbc:sql_query(Ref, " UPDATE EMPLOYEE SET AGE = 5 WHERE INCOME= 2000")]).
上述程序的输出将是:
输出
{updated,1}
从数据库中删除记录
Erlang 也能够从数据库中删除记录。
相同的示例如下:
示例
-module(helloworld). -export([start/0]). start() -> odbc:start(), {ok, Ref} = odbc:connect("DSN = usersqlserver; UID = testuser;PWD = test123", []), io:fwrite("~p",[ odbc:sql_query(Ref, "DELETE EMPLOYEE WHERE INCOME= 2000")]).
上述程序的输出如下:
输出
{updated,1}
表结构
Erlang 也能够描述表结构。
示例如下:
示例
-module(helloworld). -export([start/0]). start() -> odbc:start(), {ok, Ref} = odbc:connect("DSN = usersqlserver; UID = testuser;PWD = test123", []), io:fwrite("~p",[odbc:describe_table(Ref, "EMPLOYEE")]).
上述程序的输出如下:
输出
{ok,[{"FIRSTNAME",{sql_varchar,20}}, {"LASTNAME",{sql_varchar,20}}, {"AGE",sql_integer}, {"SEX",{sql_char,1}}, {"INCOME",sql_integer}]}
记录计数
Erlang 也能够获取表中记录的总数。
以下程序展示了一个示例。
示例
-module(helloworld). -export([start/0]). start() -> odbc:start(), {ok, Ref} = odbc:connect("DSN = usersqlserver; UID = sa;PWD = demo123", []), io:fwrite("~p",[odbc:select_count(Ref, "SELECT * FROM EMPLOYEE")]).
上述程序的输出将是:
{ok,1}
Erlang - 端口
在 Erlang 中,端口用于不同程序之间的通信。套接字是一个通信端点,允许机器通过使用互联网协议 (IP) 在互联网上进行通信。
端口中使用的协议类型
有两种可用于通信的协议。一种是 UDP,另一种是 TCP。UDP 允许应用程序相互发送短消息(称为数据报),但不能保证这些消息的投递。它们也可能乱序到达。另一方面,TCP 提供可靠的字节流,只要连接建立,这些字节流就会按顺序投递。
让我们来看一个使用 UDP 打开端口的简单示例。
示例
-module(helloworld). -export([start/0]). start() -> {ok, Socket} = gen_udp:open(8789), io:fwrite("~p",[Socket]).
关于上述程序,需要注意以下几点:
gen_udp 包含 Erlang 中用于 UDP 通信的模块。
这里 8789 是在 Erlang 中打开的端口号。您需要确保此端口号可用且可以使用。
上述程序的输出为:
#Port<0.376>
在端口上发送消息
打开端口后,可以在端口上发送消息。这是通过 send 方法完成的。让我们来看一下语法和下面的示例。
语法
send(Socket, Address, Port, Packet)
参数
Socket − 这是使用 gen_udp:open 命令创建的套接字。
Address − 这是要将消息发送到的机器地址。
port − 这是需要发送消息的端口号。
Packet − 这是需要发送的数据包或消息详细信息。
返回值
如果消息发送成功,则返回 ok 消息。
例如
-module(helloworld). -export([start/0]). start() -> {ok, Socket} = gen_udp:open(8789), io:fwrite("~p",[Socket]), io:fwrite("~p",[gen_udp:send (Socket,"localhost",8789,"Hello")]).
输出
上述程序的输出如下。
#Port<0.376>ok
在端口上接收消息
打开端口后,也可以在端口上接收消息。这是通过recv 方法完成的。让我们来看一下语法和下面的示例。
语法
recv(Socket, length)
参数
Socket − 这是使用 gen_udp:open 命令创建的套接字。
Length − 这是需要接收的消息的长度。
返回值
如果消息发送成功,则返回 ok 消息。
例如
-module(helloworld). -export([start/0]). start() -> {ok, Socket} = gen_udp:open(8789), io:fwrite("~p",[Socket]), io:fwrite("~p",[gen_udp:send(Socket,"localhost",8789,"Hello")]), io:fwrite("~p",[gen_udp:recv(Socket, 20)]).
完整的程序
显然,我们不能在同一个程序中同时使用相同的发送和接收消息。您需要在不同的程序中定义它们。因此,让我们创建以下代码,该代码创建一个侦听消息的服务器组件和一个发送消息的客户端组件。
示例
-module(helloworld). -export([start/0,client/1]). start() -> spawn(fun() -> server(4000) end). server(Port) -> {ok, Socket} = gen_udp:open(Port, [binary, {active, false}]), io:format("server opened socket:~p~n",[Socket]), loop(Socket). loop(Socket) -> inet:setopts(Socket, [{active, once}]), receive {udp, Socket, Host, Port, Bin} -> io:format("server received:~p~n",[Bin]), gen_udp:send(Socket, Host, Port, Bin), loop(Socket) end. client(N) -> {ok, Socket} = gen_udp:open(0, [binary]), io:format("client opened socket=~p~n",[Socket]), ok = gen_udp:send(Socket, "localhost", 4000, N), Value = receive {udp, Socket, _, _, Bin} -> io:format("client received:~p~n",[Bin]) after 2000 -> 0 end, gen_udp:close(Socket), Value.
关于上述程序,需要注意以下几点:
我们定义了两个函数,第一个是 server。它将用于侦听 4000 端口。第二个是 client,它将用于向服务器组件发送“Hello”消息。
接收循环用于读取在定义循环内发送的消息。
输出
现在,您需要从两个窗口运行程序。第一个窗口将用于通过在erl 命令行窗口中运行以下代码来运行服务器组件。
helloworld:start().
这将在命令行窗口中显示以下输出。
server opened socket:#Port<0.2314>
现在在第二个 erl 命令行窗口中,运行以下命令。
Helloworld:client(“<<Hello>>”).
发出此命令后,将在第一个命令行窗口中显示以下输出。
server received:<<"Hello">>
Erlang - 分布式编程
分布式程序是指那些设计为在计算机网络上运行并且只能通过消息传递来协调其活动的程序。
我们可能需要编写分布式应用程序的原因有很多。以下是一些原因。
性能 − 通过安排程序的不同部分在不同的机器上并行运行,我们可以使程序运行得更快。
可靠性 − 通过将系统构建为在多台机器上运行,我们可以构建容错系统。如果一台机器发生故障,我们可以在另一台机器上继续运行。
可扩展性 − 当我们扩展应用程序时,迟早我们会耗尽即使是最强大的机器的功能。在这个阶段,我们必须添加更多机器来增加容量。添加新机器应该是一个简单的操作,不需要对应用程序架构进行大的更改。
分布式 Erlang 中的核心概念是节点。节点是自包含的。
Erlang 系统包含一个完整的虚拟机,它具有自己的地址空间和自己的进程集。
让我们看一下用于分布式编程的各种方法。
序号 | 方法和描述 |
---|---|
1 |
这用于创建一个新进程并对其进行初始化。 |
2 |
这用于确定进程需要在其上运行的节点的值。 |
3 |
这用于在一个节点上创建一个新进程。 |
4 |
如果本地节点处于活动状态并且可以成为分布式系统的一部分,则返回 true。 |
5 |
这用于在一个节点上创建一个新的进程链接。 |
Erlang - OTP (开放电信平台)
OTP 代表开放电信平台。它是一个应用程序操作系统,以及一组用于构建大型、容错、分布式应用程序的库和程序。如果您想使用 OTP 编写自己的应用程序,那么您会发现非常有用的核心概念是 OTP 行为。行为封装了常见的行为模式——可以将其视为一个由回调模块参数化的应用程序框架。
OTP 的强大功能来自于诸如容错性、可扩展性、动态代码升级等特性,这些特性可以由行为本身提供。因此,第一个基本概念是创建一个模拟 OTP 环境基础的服务器组件,让我们来看一下以下示例。
示例
-module(server). -export([start/2, rpc/2]). start(Name, Mod) -> register(Name, spawn(fun() -> loop(Name, Mod, Mod:init()) end)). rpc(Name, Request) -> Name ! {self(), Request}, receive {Name, Response} -> Response end. loop(Name, Mod, State) -> receive {From, Request} -> {Response, State1} = Mod:handle(Request, State), From ! {Name, Response}, loop(Name, Mod, State1) end.
关于上述程序,需要注意以下几点:
该进程使用 register 函数向系统注册。
该进程生成一个处理处理的循环函数。
现在让我们编写一个将利用服务器程序的客户端程序。
示例
-module(name_server). -export([init/0, add/2, whereis/1, handle/2]). -import(server1, [rpc/2]). add(Name, Place) -> rpc(name_server, {add, Name, Place}). whereis(Name) -> rpc(name_server, {whereis, Name}). init() -> dict:new(). handle({add, Name, Place}, Dict) -> {ok, dict:store(Name, Place, Dict)}; handle({whereis, Name}, Dict) -> {dict:find(Name, Dict), Dict}.
这段代码实际上执行两项任务。它充当从服务器框架代码调用的回调模块,同时包含客户端将调用的接口例程。通常的 OTP 约定是将这两个函数组合在同一个模块中。
以下是运行上述程序的方法:
在erl中,首先通过运行以下命令运行服务器程序。
server(name_server,name_server)
您将得到以下输出:
输出
true
然后,运行以下命令
name_server.add(erlang,”Tutorialspoint”).
您将得到以下输出:
输出
Ok
然后,运行以下命令:
name_server.whereis(erlang).
您将得到以下输出:
输出
{ok,"Tutorialspoint"}
Erlang - 并发
Erlang 中的并发编程需要遵循以下基本原则或流程。
该列表包括以下原则:
piD = spawn(Fun)
创建一个新的并发进程来评估 Fun。新进程与调用者并行运行。示例如下:
示例
-module(helloworld). -export([start/0]). start() -> spawn(fun() -> server("Hello") end). server(Message) -> io:fwrite("~p",[Message]).
上述程序的输出为:
输出
“Hello”
Pid ! Message
向标识符为 Pid 的进程发送消息。消息发送是异步的。发送者不会等待,而是继续执行其正在执行的操作。‘!’ 称为发送运算符。
示例如下:
示例
-module(helloworld). -export([start/0]). start() -> Pid = spawn(fun() -> server("Hello") end), Pid ! {hello}. server(Message) -> io:fwrite("~p",[Message]).
Receive…end
接收已发送到进程的消息。它具有以下语法:
语法
receive Pattern1 [when Guard1] -> Expressions1; Pattern2 [when Guard2] -> Expressions2; ... End
当消息到达进程时,系统尝试将其与 Pattern1(可能带有 Guard1 保护)进行匹配;如果成功,则评估 Expressions1。如果第一个模式不匹配,则尝试 Pattern2,依此类推。如果没有任何模式匹配,则将消息保存以供以后处理,并且进程等待下一条消息。
以下程序显示了包含所有三个命令的整个流程的示例。
示例
-module(helloworld). -export([loop/0,start/0]). loop() -> receive {rectangle, Width, Ht} -> io:fwrite("Area of rectangle is ~p~n" ,[Width * Ht]), loop(); {circle, R} -> io:fwrite("Area of circle is ~p~n" , [3.14159 * R * R]), loop(); Other -> io:fwrite("Unknown"), loop() end. start() -> Pid = spawn(fun() -> loop() end), Pid ! {rectangle, 6, 10}.
关于上述程序,需要注意以下几点:
loop 函数具有 receive end 循环。因此,当发送消息时,它将由 receive end 循环处理。
生成一个新的进程,该进程进入 loop 函数。
通过 Pid ! message 命令将消息发送到生成的进程。
上述程序的输出为:
输出
Area of the Rectangle is 60
最大进程数
在并发中,确定系统上允许的最大进程数非常重要。然后,您应该能够理解系统上可以并发执行多少个进程。
让我们来看一个如何确定系统上可以执行的最大进程数的示例。
-module(helloworld). -export([max/1,start/0]). max(N) -> Max = erlang:system_info(process_limit), io:format("Maximum allowed processes:~p~n" ,[Max]), statistics(runtime), statistics(wall_clock), L = for(1, N, fun() -> spawn(fun() -> wait() end) end), {_, Time1} = statistics(runtime), {_, Time2} = statistics(wall_clock), lists:foreach(fun(Pid) -> Pid ! die end, L), U1 = Time1 * 1000 / N, U2 = Time2 * 1000 / N, io:format("Process spawn time=~p (~p) microseconds~n" , [U1, U2]). wait() -> receive die -> void end. for(N, N, F) -> [F()]; for(I, N, F) -> [F()|for(I+1, N, F)]. start()-> max(1000), max(100000).
在任何具有良好处理能力的机器上,上述两个 max 函数都将通过。以下是上述程序的示例输出。
Maximum allowed processes:262144 Process spawn time=47.0 (16.0) microseconds Maximum allowed processes:262144 Process spawn time=12.81 (10.15) microseconds
带超时的接收
有时,receive 语句可能会永远等待永远不会出现的邮件。这可能有很多原因。例如,我们的程序中可能存在逻辑错误,或者要向我们发送消息的进程可能在发送消息之前已经崩溃。为了避免这个问题,我们可以向 receive 语句添加超时。这设置了进程等待接收消息的最大时间。
以下是带有指定超时的 receive 消息的语法
语法
receive Pattern1 [when Guard1] -> Expressions1; Pattern2 [when Guard2] -> Expressions2; ... after Time -> Expressions end
最简单的例子是创建一个休眠函数,如下面的程序所示。
示例
-module(helloworld). -export([sleep/1,start/0]). sleep(T) -> receive after T -> true end. start()-> sleep(1000).
上述代码将在实际退出之前休眠 1000 毫秒。
选择性接收
Erlang 中的每个进程都有一个关联的邮箱。当您向进程发送消息时,该消息将放入邮箱中。仅当程序评估 receive 语句时,才会检查此邮箱。
以下是选择性 receive 语句的一般语法。
语法
receive Pattern1 [when Guard1] -> Expressions1; Pattern2 [when Guard1] -> Expressions1; ... after Time -> ExpressionTimeout end
这就是上述 receive 语句的工作方式:
当我们输入 receive 语句时,我们启动一个计时器(但仅当表达式中存在 after 部分时)。
获取邮箱中的第一条消息,并尝试将其与 Pattern1、Pattern2 等进行匹配。如果匹配成功,则将消息从邮箱中删除,并评估模式后面的表达式。
如果 receive 语句中没有任何模式与邮箱中的第一条消息匹配,则将第一条消息从邮箱中删除并放入“保存队列”中。然后尝试邮箱中的第二条消息。重复此过程,直到找到匹配的消息或检查邮箱中的所有消息。
如果邮箱中的任何消息都不匹配,则进程将被挂起,并在下次将新消息放入邮箱时重新安排执行。请注意,当收到新消息时,不会重新匹配保存队列中的消息;只会匹配新消息。
只要消息已匹配,则将所有已放入保存队列的消息按到达进程的顺序重新输入邮箱中。如果设置了计时器,则将其清除。
如果我们在等待消息时计时器到期,则评估表达式 ExpressionsTimeout 并将任何保存的消息按到达进程的顺序放回邮箱中。
Erlang - 性能
在谈论性能时,需要注意以下关于 Erlang 的几点。
Fun 非常快 − Fun 在 R6B 中获得了自己的数据类型,并在 R7B 中得到了进一步优化。
使用 ++ 运算符 − 此运算符需要以正确的方式使用。以下示例是不正确使用 ++ 操作的方法。
示例
-module(helloworld). -export([start/0]). start()-> fun_reverse([H|T]) -> fun_reverse(T)++[H]; fun_reverse([]) -> [].
由于 ++ 运算符复制其左操作数,因此结果被重复复制,导致二次复杂度。
字符串的使用 − 如果处理不当,字符串处理可能会很慢。在 Erlang 中,您需要更多地考虑字符串的使用方式并选择合适的表示方式。如果使用正则表达式,请使用 STDLIB 中的 re 模块,而不是已废弃的 regexp 模块。
BEAM 是一个基于栈的字节码虚拟机 − BEAM 是一个基于寄存器的虚拟机。它有 1024 个虚拟寄存器,用于保存临时值以及在调用函数时传递参数。需要在函数调用后仍然存在的变量将保存到栈中。BEAM 是一个线程代码解释器。每个指令都是指向可执行 C 代码的字,使指令分派非常快。
Erlang - 驱动程序
有时我们希望在 Erlang 运行时系统内运行外语程序。在这种情况下,程序被编写为共享库,并动态链接到 Erlang 运行时系统中。链接的驱动程序对程序员来说就像一个端口程序,并遵循与端口程序完全相同的协议。
创建驱动程序
创建链接的驱动程序是将外语代码与 Erlang 接口最有效的方式,但它也是最危险的方式。链接的驱动程序中的任何致命错误都将导致 Erlang 系统崩溃。
以下是 Erlang 中驱动程序实现的示例 −
示例
-module(helloworld). -export([start/0, stop/0]). -export([twice/1, sum/2]). start() -> start("example1_drv" ). start(SharedLib) -> case erl_ddll:load_driver("." , SharedLib) of ok -> ok; {error, already_loaded} -> ok; _ -> exit({error, could_not_load_driver}) end, spawn(fun() -> init(SharedLib) end). init(SharedLib) -> register(example1_lid, self()), Port = open_port({spawn, SharedLib}, []), loop(Port). stop() -> example1_lid ! stop. twice(X) -> call_port({twice, X}). sum(X,Y) -> call_port({sum, X, Y}). call_port(Msg) -> example1_lid ! {call, self(), Msg}, receive {example1_lid, Result} -> Result end. LINKED-IN DRIVERS 223 loop(Port) -> receive {call, Caller, Msg} -> Port ! {self(), {command, encode(Msg)}}, receive {Port, {data, Data}} -> Caller ! {example1_lid, decode(Data)} end, loop(Port); stop -> Port ! {self(), close}, receive {Port, closed} -> exit(normal) end; {'EXIT', Port, Reason} -> io:format("~p ~n" , [Reason]), exit(port_terminated) end. encode({twice, X}) -> [1, X]; encode({sum, X, Y}) -> [2, X, Y]. decode([Int]) -> Int.
请注意,使用驱动程序非常复杂,在使用驱动程序时应小心谨慎。
Erlang - Web编程
在 Erlang 中,可以使用inets 库来构建 Erlang 中的 Web 服务器。让我们看看 Erlang 中可用于 Web 编程的一些函数。可以实现 HTTP 服务器(也称为 httpd)来处理 HTTP 请求。
该服务器实现了许多功能,例如:
- 安全套接字层 (SSL)
- Erlang 脚本接口 (ESI)
- 公共网关接口 (CGI)
- 用户身份验证(使用 Mnesia、Dets 或纯文本数据库)
- 通用日志文件格式(带或不带 disk_log(3) 支持)
- URL 别名
- 动作映射
- 目录列表
第一步是通过命令启动 Web 库。
inets:start()
下一步是实现 inets 库的启动函数,以便可以实现 Web 服务器。
以下是创建 Erlang 中 Web 服务器进程的示例。
例如
-module(helloworld). -export([start/0]). start() -> inets:start(), Pid = inets:start(httpd, [{port, 8081}, {server_name,"httpd_test"}, {server_root,"D://tmp"},{document_root,"D://tmp/htdocs"}, {bind_address, "localhost"}]), io:fwrite("~p",[Pid]).
关于上述程序,需要注意以下几点。
端口号必须唯一,并且不能被任何其他程序使用。httpd 服务将在此端口号上启动。
server_root 和 document_root 是必需的参数。
输出
以下是上述程序的输出。
{ok,<0.42.0>}
要在 Erlang 中实现一个Hello world Web 服务器,请执行以下步骤:
步骤 1 − 实现以下代码 −
-module(helloworld). -export([start/0,service/3]). start() -> inets:start(httpd, [ {modules, [ mod_alias, mod_auth, mod_esi, mod_actions, mod_cgi, mod_dir, mod_get, mod_head, mod_log, mod_disk_log ]}, {port,8081}, {server_name,"helloworld"}, {server_root,"D://tmp"}, {document_root,"D://tmp/htdocs"}, {erl_script_alias, {"/erl", [helloworld]}}, {error_log, "error.log"}, {security_log, "security.log"}, {transfer_log, "transfer.log"}, {mime_types,[ {"html","text/html"}, {"css","text/css"}, {"js","application/x-javascript"} ]} ]). service(SessionID, _Env, _Input) -> mod_esi:deliver(SessionID, [ "Content-Type: text/html\r\n\r\n", "<html><body>Hello, World!</body></html>" ]).
步骤 2 − 按如下方式运行代码。编译上述文件,然后在erl中运行以下命令。
c(helloworld).
您将获得以下输出。
{ok,helloworld}
下一个命令是:
inets:start().
您将获得以下输出。
ok
下一个命令是:
helloworld:start().
您将获得以下输出。
{ok,<0.50.0>}
步骤 3 − 您现在可以访问 url - https://127.0.0.1:8081/erl/hello_world:service。