📜  Erlang教程(1)

📅  最后修改于: 2023-12-03 15:14:53.350000             🧑  作者: Mango

Erlang教程

Erlang Logo

简介

Erlang是一种面向并发的编程语言,特别适合构建分布式、实时、容错的应用程序。Erlang的语法类似于普通的函数式编程语言,如Scheme和Haskell,但它的并发处理、容错和错误恢复能力非常出色,因此适用于许多高并发和可靠性要求的应用场景。

在本教程中,我们将介绍Erlang的基本语法、数据类型、模块和函数、控制结构、并发模型以及错误处理等方面的内容。

安装环境

安装Erlang可以访问官方网站https://www.erlang.org/downloads,选择适合自己系统的版本下载安装即可。

基本语法
变量

在Erlang中变量以大写字母开头,可以包含字母、数字、下划线。变量在声明时可以被赋值,但是之后不能改变它的值。

1> X = 1.
1
2> Y = 2.
2
3> X = 3.   
** exception error: no match of right hand side value 3
原子

Erlang的原子用于表示常量,通常用于状态变量、消息类型等常量表达。原子以小写字母或用单引号引起来的字母和数字组成。

1> ok.
ok
2> hello.
hello
3> 'Erlang is awesome'.
'Erlang is awesome'
函数调用

函数调用的基本语法是:函数名(参数1, 参数2, ..., 参数n)。

1> io:fwrite("Hello ~s~n", ["Erlang"]).
Hello Erlang
ok
注释

在Erlang中单行注释以%开头,多行注释用/.../括起来。

% 这是一行注释

%% 这是另一行注释

/*
这是多行注释
*/
数据类型
数字

Erlang支持整数和浮点数。

1> 123.
123
2> 3.14.
3.14
布尔值

Erlang中的布尔值只有两个:true和false。

1> true.
true
2> false.
false
元组

元组是有序的值集合,使用大括号{}括起来,用逗号分隔元素。

1> {1, 2, 3}.
{1,2,3}
2> {apple, 5.0, true}.
{apple,5.0,true}
列表

列表是有序的值集合,使用中括号[]括起来,用逗号分隔元素。

1> [1, 2, 3].
[1,2,3]
2> [apple, 5.0, true].
[apple,5.0,true]
字符串

Erlang中的字符串实际上是由字符列表组成。

1> "Hello, Erlang!".
"H.e.l.l.o.,. .E.r.l.a.n.g.!."
二进制

Erlang中的二进制用<<>>括起来,用逗号分隔元素。

1> <<1, 2>>.
<<1,2>>
2> <<"Hello, Erlang!">>.
<<"Hello, Erlang!">>
Map映射

在Erlang 17.0之后,新加入了映射(Map)数据类型。

1> Map = #{a => 1, b => 2}.
#{a => 1,b => 2}
2> maps:get(a, Map).
1
模块和函数
定义模块

一个Erlang模块是一个包含函数的文件,文件名必须与模块名相同。每个模块由以下部分组成:

  • 模块声明
  • 支持声明
  • 导出声明
  • 函数定义
-module(hello_world).
-export([hello/0]).

hello() ->
    io:fwrite("Hello, World!~n").
  • -module(hello_world) 声明一个名为hello_world的模块
  • -export([hello/0]) 导出hello函数
  • hello() -> 函数定义
定义函数

函数定义由函数名、参数列表和函数体组成。

-module(my_module).
-export([double/1]).

double(X) ->
    X * 2.

上面的例子定义了一个名为double的函数,它接受一个参数X,返回X的两倍。

调用函数

调用函数的基本语法是:模块名:函数名(参数1, 参数2, ..., 参数n)。

1> my_module:double(5).
10
高阶函数

在Erlang中,函数可以作为参数传递给其他函数,也可以作为返回值返回。

-module(higher_order).
-export([times/1, apply_twice/2]).

times(N) ->
    fun(X) -> X * N end.

apply_twice(F, X) ->
    F(F(X)).

上面的代码定义了一个高阶函数times,它返回一个将其参数N与传递给它的参数相乘的匿名函数,以及一个高阶函数apply_twice,它在传递给它的函数上调用两次。

1> Times3 = higher_order:times(3).
#Fun<higher_order.1.15632420>
2> Times3(5).
15

3> higher_order:apply_twice(Times3, 5).
45
控制结构
if语句

if语句基本语法为:

if 
    Condition1 -> Action1; 
    Condition2 -> Action2; 
    true -> Action3 
end.

Erlang支持多个条件选项,如果有多个条件选项,请注意在分号后面添加空格。

-module(if_demo).
-export([check_marks/1]).

check_marks(Marks) ->
    if 
        Marks >= 80  -> io:fwrite("Grade A~n"); 
        Marks >= 60  -> io:fwrite("Grade B~n"); 
        Marks >= 40  -> io:fwrite("Grade C~n");
        true        -> io:fwrite("Grade F~n")
    end.
case语句

case语句比if语句更为灵活,可以在不同的情况下执行不同的操作。case语句的基本语法为:

case Expression of 
    Pattern1 [when Guard1] -> Action1; 
    Pattern2 [when Guard2] -> Action2; 
    ... 
    PatternN [when GuardN] -> ActionN 
end.

Erlang允许在模式中使用变量和常量,如果需要可添加when语句来进一步筛选。

-module(case_demo).
-export([check_number/1]).

check_number(X) ->
    case X of
        0 -> io:fwrite("Number is zero~n");
        1 -> io:fwrite("Number is one~n");
        2 -> io:fwrite("Number is two~n");
        _ -> io:fwrite("Unknown number~n")
    end.
循环语句

Erlang中有三种循环语句:

for循环

for循环允许在已知的循环次数内执行一个代码块。例如,我们可以使用for循环来计算一个数字列表的总和:

-module(for_loop).
-export([sum_list/1]).

sum_list(List) ->
    Length = length(List),
    Sum = for(I = 1, I =< Length, 0) do
              lists:nth(I, List)
          end,
    Sum.

while循环

while循环允许在某个条件保持为真的情况下无限循环执行一个代码块,直到条件不再为真。

-module(while_loop).
-export([countdown/1]).

countdown(X) when X > 0 ->
    io:fwrite("~w~n", [X]),
    countdown(X - 1);
countdown(0) ->
    io:fwrite("Blast off!~n").

递归

递归允许在函数内部无限循环调用自身。

-module(recursion).
-export([sum_list/1]).

sum_list([]) -> 
    0;
sum_list([H | T]) -> 
    H + sum_list(T).
并发模型

Erlang的并发性是通过它的Actor模型实现的,每个Actor代表一个独立的并发执行单元,并且不会与其他Actor共享内存或直接相互通信。相反,它们通过异步消息传递进行通信。

以下是一个可以避免死锁的例子代码:

-module(concurrency).
-export([start/0, func1/1, func2/1]).

start() ->
    Pid1 = spawn(fun() -> func1([]) end),
    Pid2 = spawn(fun() -> func2([]) end),
    timer:sleep(300),
    Pid2 ! {self(), stop}.

func1(List) ->
    receive
        {From, add, Item} ->
            NewList = lists:append(List, [Item]),
            From ! {self(), ack},
            func1(NewList);
        {From, get} ->
            From ! {self(), list, List},
            func1(List);
        {_, stop} ->
            ok
    end.

func2(List) ->
    receive
        {From, add, Item} ->
            NewList = lists:append(List, [Item]),
            From ! {self(), ack},
            func2(NewList);
        {From, get} ->
            From ! {self(), list, List},
            func2(List);
        {_, stop} ->
            ok
    end.
spawn函数

spawn函数用于在新进程中启动一个函数。

1> Pid = spawn(fun() -> io:fwrite("Hello, World!~n") end).
Hello, World!
<0.41.0>

这个函数在一个新进程中执行,我们可以使用pid来与它通信,例如发送消息或停止它。

send函数

send函数用于向另一个进程发送消息。

1> Pid ! {self(), add, 5}.
{<0.36.0>,ack}
2> Pid ! {self(), get}.
{<0.36.0>,list,[5]}
receive函数

receive函数用于从进程接收消息。

-module(receive_demo).
-export([start/0, func/0]).

start() ->
    Pid = spawn(fun() -> func() end),
    Pid ! {self(), 5},
    Pid ! {self(), 7},
    Pid ! {self(), stop}.

func() ->
    receive
        {From, X} ->
            io:fwrite("Received ~w~n", [X]),
            func();
        {From, stop} ->
            ok
    end.
错误处理

Erlang中可以通过try-catch或通过返回特殊的{error, Reason}元组来处理错误。

try-catch

try-catch结构用于捕获和处理异常。

1> try 1/0 catch _:_ -> io:fwrite("Error~n") end.
Error
ok
错误返回

在Erlang中,可以通过返回的元组中添加{error, Reason}来指示错误。

-module(error_handling).
-export([function_that_can_fail/1]).

function_that_can_fail(X) ->
    if X < 0 -> {error, "Negative number not allowed"};
       X > 10 -> {error, "Number too large"};
       true -> X * 2
    end.

在调用此函数时,您可以使用模式匹配来检查是否有错误。

1> case error_handling:function_that_can_fail(-1) of
       {error, Reason} ->
           io:fwrite("Error: ~w~n", [Reason]);
       Result ->
           io:fwrite("Result: ~w~n", [Result])
   end.
Error: Negative number not allowed
ok