Saber entrar e sair do shell do Erlang é o primeiro passo, mas não é muito útil. A diversão começa quando passamos a construir nossos próprios programas.
Eu tenho usado o Emacs para escrever meus programas em Erlang, ele tem um ambiente que colore os elementos e ajuda a fechar parênteses e completar expressões.
Linguagens funcionais são recursivas por natureza, então nada de Hello World, nada melhor do que começar com uma função recursiva como exemplo. Que tal o fatorial:
-module(lib).
-export([fac/1]).
fac(0) ->
1;
fac(N) ->
N * fac(N-1).
O código acima pode ser escrito num arquivo e compilado, e demonstra algumas coisas básicas da linguagem. Primeiro, todo comando deve terminar com um ponto ".". Na linha 1 e 2 estão algumas diretivas obrigatórias de todo código fonte, module e export. Toda diretiva de compilação começa com o sinal de "-". A diretiva module declara o nome do módulo deste arquivo. Em Erlang, cada arquivo é um módulo, ou seja, um conjunto coeso de funções. Normalmente se o módulo se chama "lib" como o exemplo, então o arquivo precisa se chamar "lib.erl", porque com esta convenção o compilador consegue encontrá-lo automaticamente. Em Erlang, uma lista de elementos é representada usando colchetes "[]" com cada elemento separado por vírgulas. Alguns exemplos de listas:
[3,78,2,7,3].
["hello", "world"].
[hello, world].
A primeira lista contêm 5 números inteiros, a segunda 2 cadeias de caracteres, e a terceira 2 átomos. Então como podemos perceber a segunda diretiva de compilação export recebe como argumento uma lista, onde cada elemento é o nome de uma função que o módulo exporta para uso externo e o número de argumentos. Em Erlang, funções com mesmo nome mas número diferente de argumentos são funções completamente diferentes. Toda função em Erlang precisa retornar algum valor.
Uma função em Erlang sempre tem o formato Assinatura -> CódigoParaExecução. A primeira parte determina o nome e os argumentos da função, enquanto a segunda contêm o que será executado. Em Erlang a execução de uma função sempre envolve comparação de padrões, e uma função pode declarar vários padrões separados por ";". Cada padrão é testado contra os argumentos, em sequência, até que algum se aplique ou caso contrário, é lançado um erro.
No caso do fatorial, o primeiro padrão é "fac(0) -> 1". Ao se chamar esta função com o primeiro argumento igual a zero, será retornado zero. O segundo padrão é "fac(N) -> N * fac(N - 1)". Ou seja, para qualquer valor de parâmetro diferente de zero, o segundo padrão é invocado, e faz uma chamada recursiva para a mesma função, até que o parâmetro seja igual a zero, e então é executado o código do primeiro padrão, e a recursão pára.
Ok, hora de compilar e executar esse código. Salve um arquivo com o nome "lib.erl" com o conteúdo acima e no mesmo diretório execute:
fbdo@Marvin:~/Projetos/Erlang$ erl
Erlang (BEAM) emulator version 5.5.5 [async-threads:0] [kernel-poll:false]
Eshell V5.5.5 (abort with ^G)
1> c(lib).
{ok,lib}
2> lib:fac(10).
3628800
3>
Como pode ser visto, invoquei o shell usando o comando "erl", compilei o código usando "c(lib)", recebi a mensagem de que a compilação teve sucesso "{ok,lib}" e então invoquei a função que acabei de compilar usando a convenção modulo:função "lib:fac(10)". E agora, para impressionar os amigos, que tal calcular o fatorial de 1000?
3> lib:fac(1000).
402387260077093773543702433923003985719374864210714632543799910429938512398629020592044\\
208486969404800479988610197196058631666872994808558901323829669944590997424504087073759\\
918823627727188732519779505950995276120874975462497043601418278094646496291056393887437\\
886487337119181045825783647849977012476632889835955735432513185323958463075557409114262\\
417474349347553428646576611667797396668820291207379143853719588249808126867838374559731\\
746136085379534524221586593201928090878297308431392844403281231558611036976801357304216\\
168747609675871348312025478589320767169132448426236131412508780208000261683151027341827\\
977704784635868170164365024153691398281264810213092761244896359928705114964975419909342\\
221566832572080821333186116811553615836546984046708975602900950537616475847728421889679\\
646244945160765353408198901385442487984959953319101723355556602139450399736280750137837\\
615307127761926849034352625200015888535147331611702103968175921510907788019393178114194\\
545257223865541461062892187960223838971476088506276862967146674697562911234082439208160\\
153780889893964518263243671616762179168909779911903754031274622289988005195444414282012\\
187361745992642956581746628302955570299024324153181617210465832036786906117260158783520\\
751516284225540265170483304226143974286933061690897968482590125458327168226458066526769\\
958652682272807075781391858178889652208164348344825993266043367660176999612831860788386\\
150279465955131156552036093988180612138558600301435694527224206344631797460594682573103\\
790084024432438465657245014402821885252470935190620929023136493273497565513958720559654\\
228749774011413346962715422845862377387538230483865688976461927383814900140767310446640\\
259899490222221765904339901886018566526485061799702356193897017860040811889729918311021\\
171229845901641921068884387121855646124960798722908519296819372388642614839657382291123\\
125024186649353143970137428531926649875337218940694281434118520158014123344828015051399\\
694290153483077644569099073152433278288269864602789864321139083506217095002597389863554\\
277196742822248757586765752344220207573630569498825087968928162753848863396909959826280\\
956121450994871701244516461260379029309120889086942028510640182154399457156805941872748\\
998094254742173582401063677404595741785160829230135358081840096996372524230560855903700\\
624271243416909004153690105933983835777939410970027753472000000000000000000000000000000\\
000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\\
000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\\
000000000000000000000000000000000000000000000
4>
Uau, isso foi rápido. E o mais impressionante: este número gigante que no meu terminal ocupa 30 linhas e que foi calculado por uma função recursiva não estourou a pilha de execução, nem o tamanho da variável. Para pessoas que, como eu, têm mais experiência com C/C++ ou Java, é quase mágico o que acaba de acontecer. Mas as magias tem nome, e se chamam otimização da recursão em cauda e conversão implícita de tipos.
Em Erlang só existem dois tipos numéricos, o inteiro e o número de ponto flutuante. E eles nunca estouram porque não têm tamanhos fixos, e sim crescem enquanto houver memória disponível.
Para linguagens funcionais, a recursão é parte essencial, já que não existem operadores de laços. Então quase todas otimizam o quanto podem as chamadas recursivas, e no caso do fatorial a otimização é trivial, já que não existem operações a serem realizadas após a chamada recursiva, o que é chamado de recursão em cauda. O que a máquina virtual Erlang faz, ao invés de empilhar a chamada recursiva, é sempre substituir o endereço de retorno da função pelo endereço da função chamada, mantendo a pilha com tamanho constante. Nada de "StackOverflowException". É como se substituíssemos a função recursiva por uma interativa equivalente:
função fatorial(x: inteiro): inteiro
var i, aux: inteiro
inicio
aux <- 1;
para i de 1 até x faça
aux <- aux * i
fim_para
fatorial <- aux
fim
E por hoje chega de diversão com funções recursivas.
