Programação sequencial

Nomenclatura de conjuntos (compreensão de listas)

O Erlang permite utilizar uma notação sucinta, baseada na "Set Compreension" da teoria de conjuntos de Zermelo-Frankel, para gerar uma nova lista:

[Expr || Condição 1, ..., Condição N]

Expr é uma expressão que será aplicada a todos os elementos da lisa.

Condição é um gerador ou um filtro:

Regras a respeitar no uso de compreensão de listas:

Existem dois operadores especiais:


» Exemplo 1

[X||X<-Lista,Condição 1,...Condição N]

(lê-se X tal que X pertence a Lista e Condição 1, ...)

O resultado do código anterior é uma lista composta pelos elementos que respeitam as condições especificadas.

1> L = [1,2,3,4,5,6,7,8,9,10].
[1,2,3,4,5,6,7,8,9,10]

2> [X||X<-L,X>5].
[6,7,8,9,10]


» Exemplo 2

1> L = [1,2,3].
[1,2,3]

2> [X*2 || X<- L].
[2,4,6]

3> L2 = [1,2,3,4.0,a].
[1,2,3,4.0,a]

4> [X*2 || X <- L2, is_integer(X)].
[2,4,6]


» Exemplo 3

select(X,L) -> [Y || {X,Y} <- L].

Ao compilar é gerado o seguinte erro:

Warning: variable 'X' shadowed in generate

Problema: X no gerador não é o mesmo X do cabeçalho da função!

Resultado:

L = select(b, [{a,1}, {b,2}, {c,3}, {b,4}]
L = [1,2,3,4]

Solução: Gerador deve ter variáveis não instanciadas

select(X,L) -> [Y || {X1,Y} <- L, X1 == X].

Resultado:

L = select(b, [{a,1}, {b,2}, {c,3}, {b,4}]
L = [2,4]


Exercícios

Implemente as seguintes funções usando compreensão de listas:

 

Records

Os records em Erlang são estruturas de dados semelhante às structs em C. Como vimos, os tuplos apenas permitem referenciar elementos pelo seu número, usando a instrução element(N,Tuplo). Se mudarmos a ordem dos elementos, retirarmos um elemento ou adicionarmos um elemento temos de alterar o código do programa. Por sua vez, os records permitem referenciar os elementos pelo nome, permitindo ao programador ignorar a ordem dos elementos.

Definir um record

Um record é definido da seguinte forma:

-record(nome_do_record,
{campo1 [= Valor1],
campo2 [= Valor2],
...
campoN [= ValorN]}).

O nome dos records e dos campos deve ser um átomo. Os valores por omissão são opcionais e sempre que não existirem o campo toma o valor undefined.

Exemplo

-record(pessoa,{nome,telefone,endereco}).

Criar uma instância


Uma instância de um record é criada da seguinte forma:
#nome_do_record{campo1 = Valor1, campo2 = Valor2, ..., campon = ValorN}

Se algum dos campos for omitido é usado o valor por omissão.

Exemplo

>P = #pessoa{nome = "Joao", telefone = '123456789'}
{pessoa, "Joao", '123456789', undefined}


Aceder a um campo

Se P for uma variável do tipo record pessoa, podemos usar a seguinte sintaxe:

Nome = P#pessoa.nome,
Endereço = P#pessoa.endereco


Actualizar um record

>P = #pessoa{nome = "Joao", telefone = '123456789', endereco = {rua xpto,porto}}
{pessoa,"Joao",'123456789',{rua xpto,porto}}

>P2 = P#pessoa{nome = "Joana"}
{pessoa,"Joana",'123456789',{rua xpto,porto}}


Se um record for usado em vários módulos, a sua definição deve ser colocada num ficheiro com a extensão '.hrl'. Os módulos acedem à sua definição através de:

-include("os_meus_records.hrl").

Exercícios


Primitivas case e if

Case

case Expr of

Padrão 1 [when Guard1] -> Sequência 1;
Padrão 1 [when Guard1] -> Sequência 1;
...
Padrão N [when GuardN] -> Sequência N

end.

Expr pode ser uma expressão aritmética, uma BIF ou uma função do utilizador. A utilização de guards é opcional. Expr é avaliada e os n padrões são sequencialmente comparados com o resultado. Se um dos padrões é verdadeiro então a sequência correspondente é avaliada.

Pelo menos um dos padrões tem de ocorrer, caso contrário é gerado um erro de run-time.

Exemplo:

validar(Msg) ->
    case Msg of
       {msg1,From,Data} ->
          tratar_msg1(Msg);
       {msg2,From,Data}
          tratar_msg2(Msg);
       Else ->
          'msg desconhecida'
    end.

Como se pode ver o case pode facilmente ser substituído por várias clausulas de uma função.

If

if

Padrão 1 -> Sequência 1;
Padrão 2 -> Sequência 2;
...
Padrão N -> Sequência N

end.

Tal como no case pelo menos um dos padrões tem de ocorrer, caso contrário é gerado um erro de run-time.

Exemplo:

maior_ou_igual(X,Y) ->
    if
       X > Y ->
          verdadeiro;
       true ->
          falso
    end.

O átomo true pode ser usado como um else.