Dada a similitude entre o if e o cond é lícito perguntar se precisaremos dos dois. Será que não é possível definir um em termos do outro ?
A seguinte definição parece ser uma resposta:
> (defun meu-if (condicao consequente alternativa) (cond (condicao consequente) (t alternativa))) meu-if > (meu-if (> 4 3) 5 6) 5 > (meu-if (> 3 4) 5 6) 6
Quando testamos o meu-if, tudo parece bem. No entanto, se escrevermos um exemplo ligeiramente diferente, algo corre mal.
> (defun divide (x y) (meu-if (zerop y) 0 (/ x y))) divide > (divide 2 0) Error:.....
O que é que está a acontecer? Tentemos seguir as regras do avaliador para explicar a situação.
A avaliação de combinações implica a aplicação da função--que é o primeiro elemento da combinação--aos valores dos restantes elementos. No exemplo (divide 2 0), a aplicação da função divide ao valor de 2 e 0 é o valor da combinação (meu-if (zerop 0) 0 (/ 2 0)), que é a aplicação da função meu-if ao valor de (zerop 0) que é t, 0 que vale 0 e (/ 2 0) que é um erro pois não se pode dividir 2 por 0.
Desta forma, a função meu-if avalia o seu último argumento cedo demais, não esperando pelo valor de (zerop 0) para saber se pode avaliar (/ 2 0).
Mas há situações piores. Consideremos a seguinte definição e aplicação da função factorial.
> (defun meu-fact (n) (meu-if (zerop n) 1 (* n (meu-fact (- n 1))))) meu-fact > (meu-fact 4) Error:.....
Nesta situação, a avaliação de combinações implica a aplicação da função que é o primeiro elemento da combinação aos valores dos restantes elementos. No exemplo (meu-fact 4), a aplicação da função meu-fact ao valor de 4 é o valor da combinação (meu-if (zerop 4) 1 (* 4 (meu-fact (- 4 1)))), que é a aplicação da função meu-if ao valor de (zerop 4) que é nil , 1 que vale 1 e (meu-fact 3) que é a aplicação da função meu-fact ao valor 3, que é o valor da combinação...
Desta forma, a função meu-if não chega sequer a completar a avaliação dos seus argumentos, não podendo determinar qual dos valores, consequente ou alternativa, retornar, repetindo indefinidamente a aplicação da função meu-fact a argumentos sucessivamente decrescentes.
Suponhamos agora a seguinte interacção com o Lisp:
> (if (> 4 3) 100 (inexistente)) 100
Segundo o modelo de avaliação que tínhamos apresentado, uma combinação é avaliada aplicando o procedimento que o primeiro elemento da combinação especifica ao valor dos restantes elementos. Nesta óptica, antes de se escolher a opção a seguir, o avaliador deveria avaliar todas elas, i.e., 100 cujo valor é 100 e a aplicação da função inexistente que devia produzir um erro pois a função ainda não foi definida. Porque será que quando testamos isto não é produzido nenhum erro?
É evidente que, de algum modo, o if não seguiu as regras do modelo de avaliação de combinações, caso contrário, teria mesmo produzido um erro. Isto sugere que if não é uma função normal mas sim algo que possui a sua própria regra de avaliação.
Uma forma especial é uma expressão da linguagem que possui a sua própria sintaxe e a sua própria regra de avaliação. É de salientar que uma forma especial não é uma função. Ela faz parte da estrutura do avaliador e não do seu ambiente.
O defun, o if e o cond são algumas das formas especiais. Mas o and e o or também são, pois só avaliam os operandos que forem necessários para determinar o resultado. O and pára de avaliar quando um deles produzir falso, o or quando um deles produzir verdade.
Como uma forma especial possui a sua própria regra de avaliação, nem tudo o que se faz com uma função se pode fazer com uma forma especial, e nem tudo o que se faz com uma forma especial se pode fazer com uma função.
Ao contrário das outras linguagens que possuem muitas formas especiais, Lisp tem muito poucas. Desta forma, a linguagem possui uma regra de avaliação muito simples e muito bem definida, e em que as pequenas excepções são implementadas pelas formas especiais.
No entanto, e ao contrário do que acontece na maioria das outras linguagens, Lisp permite ao utilizador definir novas formas cujo comportamento é semelhante ao das formas especiais. Isso é feito através de macros, que são formas que são transformadas noutras formas (especiais ou não) durante a interpretação ou a compilação.