Funções • Capítulo 12

O que é uma Função


Funções são a essência de toda programação. Nos primeiros dias da ciência da computação elas representavam um sonho ingênuo. A ideia era que poderiam reduzir a computação em pedaços cada vez menores de código reusável. Com o tempo e uma estrutura apropriada para bibliotecas, eventualmente teríamos escrito código para todas as necessidades computacionais. Não mais as pessoas teriam que escrever suas próprias funções, e programação consistiria de um trabalho fácil de costurar componentes uns aos outros.

Este sonho não se tornou realidade ainda, mas ele persiste, não importa quão falho. Cada nova técnica de programação ou paradigma que aparece agita essa ideia um pouco. Eles prometem melhor reuso de código, melhores abstrações e uma vida mais fácil para todos.

bananas

Banana-phone • Outro sonho ingênuo.

Na realidade, o que cada paradigma oferece são simplesmente abstrações diferentes. Há sempre um trade-off. Para cada nível mais alto a se pensar sobre programação, alguma peça é perdida. E isto significa que, não importa quão bem você decide o que manter e o que deixar, ocasionalmente alguém precisará daquela peça que foi perdida. Mas através disso tudo, de uma maneira ou de outra, funções sempre persistiram, e continuamente provaram ser efetivas.

Nós usamos funções em C, nós sabemos o que elas se parecem, mas nós ainda não sabemos exatamente o que elas são. Aqui vão algumas maneiras de pensar sobre elas.

Uma maneira de pensar sobre funções é uma descrição de alguma computação que você quer executar depois. Quando você define uma função é como dizer "quando eu uso este nome, eu quero que esse tipo de coisa aconteça". É uma ideia muito prática de uma função. É muito intuitiva, e metafórica para a linguagem. Esta é a maneira de comandar um humano ou animal. Outro motivo pelo qual gosto dela é que captura a natureza adiada das funções. Funções são definidas uma vez, mas podem ser chamadas repetidamente depois.

Uma outra maneira de pensar sobre funções é como uma caixa preta que recebe alguma entrada e produz uma saída. Esta ideia é sutilmente diferente da anterior. É um pouco mais algébrica, e não fala sobre computação ou comandos. Esta ideia é um conceito matemático, e não está amarrada a alguma maquina ou linguagem em particular. Em algumas situações esta ideia é excepcionalmente útil. Ela nos permite pensar sobre funções sem se preocupar sobre suas partes internas ou como elas são computadas exatamente. Podemos então combinar ou compor funções umas com as outras sem se preocupar com alguma coisa sutil dar errado. Isto é a ideia central por trás de uma abstração, e é isto que permite camadas de complexidade funcionar juntamente com as outras ao invés de conflitar. A força desta ideia pode ser também a sua queda. Como ela não menciona nada sobre computação, ela não lida com algumas preocupações do mundo real. "Quanto tempo esta função leva para rodar?", "Esta função é eficiente?", "Vai modificar o estado do meu programa? Caso sim, como?".

black_box

Caixa Preta • Sua função típica.

Um terceiro método é pensar em funções como computações parciais. Como o modelo matemático, elas podem receber algumas entradas. Estes valores são requeridos antes que a função possa completar a computação. É por isso que ela é chamada parcial. Mas como o modelo computacional, o corpo da função consiste de uma computação especificado em alguma linguagem de comandos. Estas entradas são chamadas variáveis não vinculadas (unbounded variables), e para terminar a computação alguém precisa simplesmente fornecê-las. Como encaixar uma engrenagem em uma máquina previamente rodando sem objetivo, isto completa tudo que é necessário para a computação rodar, e a máquina roda. A saída dessas computações parciais é ela mesma uma variável com um valor desconhecido. Esta saída pode ser usada como entrada para uma nova função, e assim uma função depende de outra.

Uma vantagem dessa ideia sobre o modelo matemático é reconhecermos que funções contêm computação. Vemos que quando a computação roda, algum processo físico está acontecendo na máquina. Isto significa que reconhecemos o fato que algumas coisas tomam tempo para decorrer, ou que a função pode mudar o estado do programa, ou fazer qualquer outra coisa que não temos muita certeza.

Todas essas ideias são exploradas no estudo de funções, cálculo Lambda. Este é um campo que combina lógica, matemática e ciência da computação. O nome vem da letra grega Lambda, que é usada na representação de vinculação de variáveis (binding variables). Usar o cálculo Lambda nos dá uma maneira de definir, compor e construir funções usando uma notação matemática simples.

Vamos usar todas as ideias anteriores para adicionar funções definidas pelo usuário à nossa linguagem. Lisp já é bem adequada a este tipo de diversão, e usando estes conceitos não vai levar muito trabalho para implementar funções.

O primeiro passo será escrever uma função builtin que possa criar funções definidas pelo usuário. Aqui vai uma ideia sobre como ela pode ser especificada. O primeiro argumento poderia ser uma lista de símbolos, da mesma forma que a função def. Estes símbolos podemos chamar de argumentos formais, também conhecidos como variáveis não vinculadas. Elas agem como entradas para nossa computação parcial. O segundo argumento poderia ser uma outra lista. Quando rodamos a função, isto vai ser avaliado com nossa função builtin eval.

Esta função chamaremos apenas \, (uma homenagem ao cálculo Lambda já que o caractere \ se parece um pouco com um lambda). Para criar uma função que recebe duas entradas e as soma, poderíamos então criar algo como isso.

\ {x y} {+ x y}

Podemos chamar a função colocando-a como o primeiro argumento em uma S-Expression normal.

(\ {x y} {+ x y}) 10 20

Se quisermos nomear esta função, podemos passá-la para nosso builtin existente def como qualquer outro valor, e armazená-la no ambiente.

def {add-together} (\ {x y} {+ x y})

A seguir, poderemos chamá-la se referindo pelo nome.

add-together 10 20

Tipo função


Para armazenar uma função como um lval precisamos pensar exatamente do que ela consiste.

Usando a definição anterior, uma função deve consistir de três partes. Primeiro é a lista de argumentos formais, que precisamos vincular antes que possamos avaliar a função. A segunda parte é uma Q-Expression que representa o corpo da função. Finalmente, requeremos um local para armazenar os valores atribuídos para os argumentos formais. Com sorte, já temos uma estrutura para armazenar variáveis, um ambiente.

Vamos armazenar nossas funções builtins e funções definidas pelo usuário com o mesmo tipo LVAL_FUN. Isto significa que precisaremos duma maneira de diferenciar internamente entre elas. Para fazer isso, podemos checar se o apontador de função lbuiltin é NULL ou não. Se não for NULL, sabemos que o lval é alguma função builtin, caso contrário sabemos que é uma função do usuário.

struct lval {
  int type;

  /* Basic */
  long num;
  char* err;
  char* sym;

  /* Function */
  lbuiltin builtin;
  lenv* env;
  lval* formals;
  lval* body;

  /* Expression */
  int count;
  lval** cell;
};

Renomeamos o campo lbuiltin de func para builtin. Devemos nos certificar de mudar isto em todos os locais que é usado em nosso código.

Também precisamos criar um construtor para as funções lval definidas pelo usuário. Aqui construímos um ambiente para a função, e atribuímos os valores formals e body para os valores passados.

lval* lval_lambda(lval* formals, lval* body) {
  lval* v = malloc(sizeof(lval));
  v->type = LVAL_FUN;

  /* Seta Builtin para Null */
  v->builtin = NULL;

  /* Constroi novo ambiente */
  v->env = lenv_new();

  /* Seta Formals e Body */
  v->formals = formals;
  v->body = body;
  return v;  
}

Como sempre quando mudamos nosso tipo lval, precisamos atualizar as funções para deleção, cópia, e impressão para lidar com as mudanças. Para avaliação, precisaremos olhar com mais profundidade.

Para Deleção...

case LVAL_FUN: 
  if (!v->builtin) {
    lenv_del(v->env);
    lval_del(v->formals);
    lval_del(v->body);
  }
break;

Para Cópia...

case LVAL_FUN:
  if (v->builtin) {
    x->builtin = v->builtin;
  } else {
    x->builtin = NULL;
    x->env = lenv_copy(v->env);
    x->formals = lval_copy(v->formals);
    x->body = lval_copy(v->body);
  }
break;

Para Impressão...

case LVAL_FUN:
  if (v->builtin) {
    printf("<builtin>");
  } else {
    printf("(\\ "); lval_print(v->formals);
    putchar(' '); lval_print(v->body); putchar(')');
  }
break;

Função Lambda


Podemos adicionar um builtin para nossa função lambda. Queremos que receba como entrada uma lista de símbolos, e uma lista que represente o código. Depois disso, ela deve retornar uma função lval. Definimos já alguns builtins, e este seguirá o mesmo formato. Como em def, fazemos alguma checagem de erro para verificar que o número e os tipos dos argumentos (usando algumas Macros recém definidas). A seguir, apenas extraímos os primeiros dois argumentos da lista e os passamos para nossa função lval_lambda definida anteriormente.

lval* builtin_lambda(lenv* e, lval* a) {
  /* Verifica dois argumentos, cada um deve ser uma Q-Expression */
  LASSERT_NUM("\\", a, 2);
  LASSERT_TYPE("\\", a, 0, LVAL_QEXPR);
  LASSERT_TYPE("\\", a, 1, LVAL_QEXPR);
  
  /* Verifica que a primeira Q-Expression contem apenas Symbols */
  for (int i = 0; i < a->cell[0]->count; i++) {
    LASSERT(a, (a->cell[0]->cell[i]->type == LVAL_SYM),
      "Cannot define non-symbol. Got %s, Expected %s.",
      ltype_name(a->cell[0]->cell[i]->type),ltype_name(LVAL_SYM));
  }
  
  /* Extrai os primeiros dois argumentos e passa-os para lval_lambda */
  lval* formals = lval_pop(a, 0);
  lval* body = lval_pop(a, 0);
  lval_del(a);
  
  return lval_lambda(formals, body);
}

De onde vieram LASSERT_NUM e LASSERT_TYPE?

Tomei a liberdade de melhorar a comunicação de erros para este capítulo. Esta tarefa foi sugerida como meta bônus do capítulo anterior. Ela torna o código bem mais claro que era difícil de ignorar!

Caso esteja planejando completar esta tarefa você mesmo, agora pode ser uma boa hora de fazê-lo. Caso contrário, você pode olhar o código de referência deste capítulo para ver a abordagem que usei, e integrá-la em seu código.

playgroup

Pré-escola • Típico ambiente pai.

Ambiente Pai


Demos às funções seus próprios ambientes. Neste ambiente colocaremos os valores que seus argumentos formais são setados. Quando vamos avaliar o corpo da função, podemos fazê-lo neste ambiente e saber que aquelas variáveis terão os valores corretos.

Mas idealmente também queremos que estas funções sejam capaz de acessar variáveis que estão no ambiente global, como as nossas funções builtins.

Podemos resolver este problema mudando a definição do nosso ambiente para conter uma referência a algum ambiente pai. A seguir, quando queremos avaliar uma função, podemos setar este ambiente pai para nosso ambiente global, que tem todas nossas builtins definidas dentro dele.

Quando adicionamos isto ao nosso struct lenv, conceitualmente será uma referência a um ambiente pai, não algum subambiente ou algo do tipo. Por causa disso, não devemos deletá-lo quando nosso lenv é deletado, ou copiá-lo quando nosso lenv é copiado.

A maneira que o ambiente pai funciona é simples. Se alguém chama lenv_get no ambiente e o símbolo não é encontrado, ele vai buscar no ambiente pai para ver se o valor nomeado existe lá, e repetir o processo até que ou a variável seja encontrada ou não há mais pais. Para significar que um ambiente não tem pai, setamos a referência para NULL.

A função construtora somente requer mudanças básicas para permitir isto.

struct lenv {
  lenv* par;
  int count;
  char** syms;
  lval** vals;
};

lenv* lenv_new(void) {
  lenv* e = malloc(sizeof(lenv));
  e->par = NULL;
  e->count = 0;
  e->syms = NULL;
  e->vals = NULL;
  return e;
}

Para obter um valor de um ambiente precisamos adicionar a busca do ambiente pai caso um símbolo não for encontrado.

lval* lenv_get(lenv* e, lval* k) {

  for (int i = 0; i < e->count; i++) {
    if (strcmp(e->syms[i], k->sym) == 0) {
      return lval_copy(e->vals[i]);
    }
  }

  /* Caso sem simbolo, verifica no ambiente pai ou devolve erro */
  if (e->par) {
    return lenv_get(e->par, k);
  } else {
    return lval_err("Unbound Symbol '%s'", k->sym);
  }
}

Como temos um novo tipo lval que tem seu próprio ambiente precisamos uma função para copiar ambientes, para usar quando copiamos structs lval.

lenv* lenv_copy(lenv* e) {
  lenv* n = malloc(sizeof(lenv));
  n->par = e->par;
  n->count = e->count;
  n->syms = malloc(sizeof(char*) * n->count);
  n->vals = malloc(sizeof(lval*) * n->count);
  for (int i = 0; i < e->count; i++) {
    n->syms[i] = malloc(strlen(e->syms[i]) + 1);
    strcpy(n->syms[i], e->syms[i]);
    n->vals[i] = lval_copy(e->vals[i]);
  }
  return n;
}

Ter ambientes pais também muda nosso conceito de definir uma variável.

Há duas maneiras que podemos definir uma variável agora. Podemos ou defini-la no ambiente local, mais interno, ou podemos defini-lo no ambiente global, mais externo. Vamos adicionar funções para fazer ambos. Manteremos a função lenv_put do mesmo jeito. Ele pode ser usado para a definição no ambiente local. Mas adicionaremos uma nova função lenv_def para definição no ambiente global. Ela funciona simplesmente seguindo a cadeia de pais acima, e por fima usa lenv_put para defini-lo localmente.

void lenv_def(lenv* e, lval* k, lval* v) {
  /* Itera ate e nao ter pai */
  while (e->par) { e = e->par; }
  /* Coloca valor em e */
  lenv_put(e, k, v);
}

No momento, esta distinção pode parecer inútil, mas mais tarde vamos usá-la para escrever resultados parciais de cálculos em variáveis locais dentro de uma função. Podemos adicionar outra builtin para atribuição local. Chamaremos esta de put em C, mas a daremos o símbolo = em Lisp. Podemos adaptar nossa função builtin_def e reusar o mesmo código comum, da mesma forma que fazemos com nossos operadores matemáticos.

Em seguida, precisamos registrar estes como builtins.

lenv_add_builtin(e, "def", builtin_def);
lenv_add_builtin(e, "=",   builtin_put);
lval* builtin_def(lenv* e, lval* a) {
  return builtin_var(e, a, "def");
}
lval* builtin_put(lenv* e, lval* a) {
  return builtin_var(e, a, "=");
}
lval* builtin_var(lenv* e, lval* a, char* func) {
  LASSERT_TYPE(func, a, 0, LVAL_QEXPR);
  
  lval* syms = a->cell[0];
  for (int i = 0; i < syms->count; i++) {
    LASSERT(a, (syms->cell[i]->type == LVAL_SYM),
      "Function '%s' cannot define non-symbol. "
      "Got %s, Expected %s.", func, 
      ltype_name(syms->cell[i]->type),
      ltype_name(LVAL_SYM));
  }
  
  LASSERT(a, (syms->count == a->count-1),
    "Function '%s' passed too many arguments for symbols. "
    "Got %i, Expected %i.", func, syms->count, a->count-1);
    
  for (int i = 0; i < syms->count; i++) {
    /* Caso 'def', define globalmente. Caso 'put', define localmente */
    if (strcmp(func, "def") == 0) {
      lenv_def(e, syms->cell[i], a->cell[i+1]);
    }
    
    if (strcmp(func, "=")   == 0) {
      lenv_put(e, syms->cell[i], a->cell[i+1]);
    } 
  }
  
  lval_del(a);
  return lval_sexpr();
}

Chamando Funções


Precisamos escrever o código que roda quando uma expressão é avaliada e uma função lval é chamada.

Quando o tipo desta função é um builtin, chamamo-la como antes, usando o apontador para função, mas precisamos fazer algo separado para nossas funções definidas pelo usuário. Precisamos vincular cada um dos argumentos passados para cada um dos símbolos no campo formals. Feito isso, precisamos avaliar o campo body, usar o campo env como um ambiente, e o ambiente chamador como um pai.

Uma primeira tentativa, sem checagem de erros, pode se parecer com isto:

lval* lval_call(lenv* e, lval* f, lval* a) {

  /* Se for Builtin, apenas chame-a */
  if (f->builtin) { return f->builtin(e, a); }

  /* Atribui cada argumento para cada formal em ordem */
  for (int i = 0; i < a->count; i++) {
      lenv_put(f->env, f->formals->cell[i], a->cell[i]);
  }

  lval_del(a);

  /* Seta o ambiente pai */
  f->env->par = e;

  /* Avalia o body */
  return builtin_eval(f->env, 
    lval_add(lval_sexpr(), lval_copy(f->body)));
}

Mas isto não funciona corretamente quando o número de argumentos fornecidos e o número de argumentos formais forem diferentes. Nesta situação ela vai falhar.

Na verdade este é um caso interessante, e nos deixa com algumas opções. Nós poderíamos simplesmente jogar um erro quando a contagem de argumentos fornecidos está incorreta, mas podemos fazer algo que é ainda mais divertido. Quando faltam argumentos a ser fornecidos, poderíamos vincular apenas os primeiros argumentos formais da função e devolvê-la, deixando o restante não vinculado.

Isto cria uma função que é chamada parcialmente avaliada e reflete nossa ideia anterior de uma função ser um tipo de computação parcial. Se começamos com uma função que recebe dois argumentos e passamos apenas um argumento, podemos vincular este primeiro argumento e retornar uma nova função com o primeiro argumento formal vinculado, e o segundo permanecendo vazio.

Esta metáfora cria uma imagem bonitinha de como as funções funcionam. Podemos imaginar uma função no começo de uma expressão, repetidamente consumindo entradas diretamente à sua direita. Depois de consumir a primeira entrada à sua direita, se ela for completa (não requer mais entradas), ela a avalia e substitui a si mesma com um novo valor. Caso contrário, se ainda requer mais argumentos, ela se substitui com uma outra função, mais completa, com algumas variáveis ligadas. Este processo se repete até que o valor final para o programa seja criado.

Então você pode imaginar funções como um pequeno Pac-Man, não consumindo todas as entradas de uma vez só, mas iterativamente comendo entradas à direita, ficando cada vez maior, até que fique cheio e exploda para criar algo novo. Isto não exatamente como vamos implementar no código, mas ainda é divertido de imaginar.

lval* lval_call(lenv* e, lval* f, lval* a) {

  /* Caso Builtin, entao aplique-a */
  if (f->builtin) { return f->builtin(e, a); }

  /* Grava contagem de argumentos */
  int given = a->count;
  int total = f->formals->count;

  /* Enquanto ainda tem argumentos a processar */
  while (a->count) {

    /* Caso acabamos de vinucular todos argumentos formais */
    if (f->formals->count == 0) {
      lval_del(a); return lval_err(
        "Function passed too many arguments. "
        "Got %i, Expected %i.", given, total); 
    }

    /* Extrai o primeiro simbolo dos formais */
    lval* sym = lval_pop(f->formals, 0);

    /* Extrai o proximo argumento da lista */
    lval* val = lval_pop(a, 0);

    /* Vincula uma copia no ambiente da funcao */
    lenv_put(f->env, sym, val);

    /* Deleta simbolo e valor */
    lval_del(sym); lval_del(val);
  }

  /* Lista de argumentos estah agora vinculada e pode ser limpa */
  lval_del(a);

  /* Se todos formais foram vinculados, entao avalia */
  if (f->formals->count == 0) {

    /* Seta ambiente pai para ambiente de avaliacao */
    f->env->par = e;

    /* Avalia e devolve */
    return builtin_eval(
      f->env, lval_add(lval_sexpr(), lval_copy(f->body)));
  } else {
    /* Caso contrario, retorna funcao parcialmente avaliada */
    return lval_copy(f);
  }

}

A função acima faz exatamente o que explicamos, adicionando também um tratamento de erros correto. Primeiro itera sobre os argumentos passados, tentando colocar cada um no ambiente. A seguir ela verifica se o ambiente está cheio, e nesse caso avalia, caso não, devolve uma cópia de si mesma com alguns argumentos preenchidos.

Se atualizamos nossa função de avaliação lval_eval_sexpr para chamar lval_call, podemos testar nosso novo sistema.

lval* f = lval_pop(v, 0);
if (f->type != LVAL_FUN) {
  lval* err = lval_err(
    "S-Expression starts with incorrect type. "
    "Got %s, Expected %s.",
    ltype_name(f->type), ltype_name(LVAL_FUN));
  lval_del(f); lval_del(v);
  return err;
}

lval* result = lval_call(e, f, v);

Experimente definir algumas funções e testar como avaliações parciais funcionam.

lispy> def {add-mul} (\ {x y} {+ x (* x y)})
()
lispy> add-mul 10 20
210
lispy> add-mul 10
(\ {y} {+ x (* x y)})
lispy> def {add-mul-ten} (add-mul 10)
()
lispy> add-mul-ten 50
510
lispy>

Argumentos Variáveis


Definimos algumas de nossas funções builtins de forma que recebam um número variável de argumentos. Funções como + e join podem receber qualquer número de argumentos, e operam neles logicamente. Devemos achar uma maneira de permitir funções definidas pelo usuário funcionar com múltiplos argumentos também.

Infelizmente não há uma maneira elegante de permitir isso, sem adicionarmos alguma sintaxe especial. Então vamos codificar algum sistema fixo na nossa linguagem usando um símbolo especial &.

Vamos permitir que usuários definam argumentos formais que se pareçam com {x & xs}, o que significa que uma função receberá um único argumento x, seguido de zero ou mais outros argumentos, unidos em uma lista chamada xs. Esta é um pouco parecida com a ellipsis que usamos para declara argumentos variáveis em C.

Quando atribuirmos nosso argumentos formais, vamos procurar por um símbolo & e caso existir, tomaremos o próximo argumento formal e o atribuiremos quaisquer argumentos fornecidos restantes que foram passados. É importante converter esta lista de argumento em uma Q-Expression. Precisamos também lembrar de checar que & seja seguido por um símbolo real, e se não for devemos devolver um erro.

Logo depois do primeiro símbolo ser extraído dos argumentos formais no laço while de lval_call, podemos adicionar este caso especial.


/* Caso especial para tratar '&' */
if (strcmp(sym->sym, "&") == 0) {

  /* Verifica que '&' eh seguido de outro simbolo */
  if (f->formals->count != 1) {
    lval_del(a);
    return lval_err("Function format invalid. "
      "Symbol '&' not followed by single symbol.");
  }

  /* Proximo formal deve ser vinculado aos argumentos restantes */
  lval* nsym = lval_pop(f->formals, 0);
  lenv_put(f->env, nsym, builtin_list(e, a));
  lval_del(sym); lval_del(nsym);
  break;
}

Suponha que ao chamar a função, o usuário não forneça quaisquer argumentos variáveis, mas apenas os primeiros argumentos nomeados. Neste caso, precisamos setar o símbolo seguindo & para a lista vazia. Adicione este caso especial logo depois de deletarmos a lista de argumentos e antes de checar para ver se todos os formais foram avaliados.


/* Caso '&' sobrou na lista formal, vincula para lista vazia */
if (f->formals->count > 0 &&
  strcmp(f->formals->cell[0]->sym, "&") == 0) {

  /* Certifica-se que & nao foi passado de maneira invalida */
  if (f->formals->count != 2) {
    return lval_err("Function format invalid. "
      "Symbol '&' not followed by single symbol.");
  }

  /* Extrai e deleta simbolo '&' */
  lval_del(lval_pop(f->formals, 0));

  /* Extrai proximo simbolo e cria lista vazia */
  lval* sym = lval_pop(f->formals, 0);
  lval* val = lval_qexpr();

  /* Vincula ao ambiente e deleta */
  lenv_put(f->env, sym, val);
  lval_del(sym); lval_del(val);
}

Funções Interessantes


Definição de função

Lambdas são claramente uma maneira simples e poderosa de definir funções. Mas a sintaxe é um pouco desajeitada. Há várias parênteses, chaves e símbolos envolvidos. Aqui vai uma ideia interessante. Podemos tentar escrever uma função que ela própria define uma função, usando uma sintaxe mais simples.

Essencialmente o que queremos é uma função que pode executar dois passos em uma vez só. Primeiro ela deve criar uma função, em seguida ela deve defini-la com um nome. Aqui está o truque. Podemos deixar o usuário fornecer o nome e os argumentos formais juntamente em uma lista, e então separar estes e usá-los na definição. Aqui está uma função que faz isso. Ela recebe como entrada alguns argumentos e um corpo. Ela recebe a cabeça dos argumentos como sendo o nome da função, e o resto para ser os argumentos formais. Ela passa o corpo diretamente a um lambda.

\ {args body} {def (head args) (\ (tail args) body)}

Podemos nomear esta função em algo como fun passando-a para def como de costume.

def {fun} (\ {args body} {def (head args) (\ (tail args) body)})

Isto significa que podemos agora definir funções de maneira muito mais simples e atraente. Para definir nossa função add-together mencionada anteriormente podemos fazer como segue. Funções podem definir funções. Isto é certamente algo que nunca poderíamos fazer em C. Como isso é legal!

fun {add-together x y} {+ x y}
curry

Currying • Não tão bom quanto parece.

Currying

Em algum momento, funções como + recebem um número variável de argumentos. Em algumas situações isto é ótimo, mas e se tivéssemos uma lista de argumentos que desejássemos passá-la? Nesta situação, ela se torna inútil.

Novamente podemos tentar criar uma função para resolver este problema. Se pudermos criar uma lista no formato que desejamos usar para nossa expressão, podemos usar eval para tratá-lo como tal. Na situação de + podemos acrescentar esta função na frente da lista e então executar a avaliação.

Podemos definir uma função unpack (do inglês, desempacotar) que faça isto. Ela recebe como entrada uma função e uma lista, acrescenta a função à frente da lista e a avalia.

fun {unpack f xs} {eval (join (list f) xs)}

Em algumas situações podemos encarar o dilema oposto. Temos uma função que recebe como entrada uma lista, mas que desejamos chamá-la usando argumentos variáveis. Neste caso a solução é ainda mais simples. Usamos o fato que nossa sintaxe & para argumentos variáveis empacota argumentos variáveis em uma lista para nós.

fun {pack f & xs} {f xs}

Em algumas linguagens isto é chamado currying e uncurrying, respectivamente. O nome é homenagem a Haskell Curry e infelizmente não tem nada a ver com nosso tempero apimentado favorito.

lispy> def {uncurry} pack
()
lispy> def {curry} unpack
()
lispy> curry + {5 6 7}
18
lispy> uncurry head 5 6 7
{5}

Por causa da maneira que nossa avaliação parcial funciona, não precisamos pensar em currying com algum conjunto específico de argumentos. Podemos pensar em funções elas mesmas sendo em forma curried ou uncurried (não curried).

lispy> def {add-uncurried} +
()
lispy> def {add-curried} (curry +)
()
lispy> add-curried {5 6 7}
18
lispy> add-uncurried 5 6 7
18

Brinque um pouco e veja quais outras funções interessantes e poderosas você consegue inventar. No próximo capítulo vamos adicionar condicionais, o que vai realmente começar a fazer nossa linguagem mais completa. Mas isto não significa que você não poderá inventar outras ideias interessantes. Nosso Lisp está ficando mais rico.

Referência


#include "mpc.h"

#ifdef _WIN32

static char buffer[2048];

char* readline(char* prompt) {
  fputs(prompt, stdout);
  fgets(buffer, 2048, stdin);
  char* cpy = malloc(strlen(buffer)+1);
  strcpy(cpy, buffer);
  cpy[strlen(cpy)-1] = '\0';
  return cpy;
}

void add_history(char* unused) {}

#else
#include <editline/readline.h>
#include <editline/history.h>
#endif

/* Forward Declarations */

struct lval;
struct lenv;
typedef struct lval lval;
typedef struct lenv lenv;

/* Lisp Value */

enum { LVAL_ERR, LVAL_NUM, LVAL_SYM, LVAL_FUN, LVAL_SEXPR, LVAL_QEXPR };

typedef lval*(*lbuiltin)(lenv*, lval*);

struct lval {
  int type;

  /* Basic */
  long num;
  char* err;
  char* sym;
  
  /* Function */
  lbuiltin builtin;
  lenv* env;
  lval* formals;
  lval* body;
  
  /* Expression */
  int count;
  lval** cell;
};

lval* lval_num(long x) {
  lval* v = malloc(sizeof(lval));
  v->type = LVAL_NUM;
  v->num = x;
  return v;
}

lval* lval_err(char* fmt, ...) {
  lval* v = malloc(sizeof(lval));
  v->type = LVAL_ERR;  
  va_list va;
  va_start(va, fmt);  
  v->err = malloc(512);  
  vsnprintf(v->err, 511, fmt, va);  
  v->err = realloc(v->err, strlen(v->err)+1);
  va_end(va);  
  return v;
}

lval* lval_sym(char* s) {
  lval* v = malloc(sizeof(lval));
  v->type = LVAL_SYM;
  v->sym = malloc(strlen(s) + 1);
  strcpy(v->sym, s);
  return v;
}

lval* lval_builtin(lbuiltin func) {
  lval* v = malloc(sizeof(lval));
  v->type = LVAL_FUN;
  v->builtin = func;
  return v;
}

lenv* lenv_new(void);

lval* lval_lambda(lval* formals, lval* body) {
  lval* v = malloc(sizeof(lval));
  v->type = LVAL_FUN;
  
  /* Set Builtin to Null */
  v->builtin = NULL;
  
  /* Build new environment */
  v->env = lenv_new();
  
  /* Set Formals and Body */
  v->formals = formals;
  v->body = body;
  return v;  
}

lval* lval_sexpr(void) {
  lval* v = malloc(sizeof(lval));
  v->type = LVAL_SEXPR;
  v->count = 0;
  v->cell = NULL;
  return v;
}

lval* lval_qexpr(void) {
  lval* v = malloc(sizeof(lval));
  v->type = LVAL_QEXPR;
  v->count = 0;
  v->cell = NULL;
  return v;
}

void lenv_del(lenv* e);

void lval_del(lval* v) {

  switch (v->type) {
    case LVAL_NUM: break;
    case LVAL_FUN: 
      if (!v->builtin) {
        lenv_del(v->env);
        lval_del(v->formals);
        lval_del(v->body);
      }
    break;
    case LVAL_ERR: free(v->err); break;
    case LVAL_SYM: free(v->sym); break;
    case LVAL_QEXPR:
    case LVAL_SEXPR:
      for (int i = 0; i < v->count; i++) {
        lval_del(v->cell[i]);
      }
      free(v->cell);
    break;
  }
  
  free(v);
}

lenv* lenv_copy(lenv* e);

lval* lval_copy(lval* v) {
  lval* x = malloc(sizeof(lval));
  x->type = v->type;
  switch (v->type) {
    case LVAL_FUN:
      if (v->builtin) {
        x->builtin = v->builtin;
      } else {
        x->builtin = NULL;
        x->env = lenv_copy(v->env);
        x->formals = lval_copy(v->formals);
        x->body = lval_copy(v->body);
      }
    break;
    case LVAL_NUM: x->num = v->num; break;
    case LVAL_ERR: x->err = malloc(strlen(v->err) + 1);
      strcpy(x->err, v->err);
    break;
    case LVAL_SYM: x->sym = malloc(strlen(v->sym) + 1);
      strcpy(x->sym, v->sym);
    break;
    case LVAL_SEXPR:
    case LVAL_QEXPR:
      x->count = v->count;
      x->cell = malloc(sizeof(lval*) * x->count);
      for (int i = 0; i < x->count; i++) {
        x->cell[i] = lval_copy(v->cell[i]);
      }
    break;
  }
  return x;
}

lval* lval_add(lval* v, lval* x) {
  v->count++;
  v->cell = realloc(v->cell, sizeof(lval*) * v->count);
  v->cell[v->count-1] = x;
  return v;
}

lval* lval_join(lval* x, lval* y) {  
  for (int i = 0; i < y->count; i++) {
    x = lval_add(x, y->cell[i]);
  }
  free(y->cell);
  free(y);  
  return x;
}

lval* lval_pop(lval* v, int i) {
  lval* x = v->cell[i];  
  memmove(&v->cell[i],
    &v->cell[i+1], sizeof(lval*) * (v->count-i-1));  
  v->count--;  
  v->cell = realloc(v->cell, sizeof(lval*) * v->count);
  return x;
}

lval* lval_take(lval* v, int i) {
  lval* x = lval_pop(v, i);
  lval_del(v);
  return x;
}

void lval_print(lval* v);

void lval_print_expr(lval* v, char open, char close) {
  putchar(open);
  for (int i = 0; i < v->count; i++) {
    lval_print(v->cell[i]);    
    if (i != (v->count-1)) {
      putchar(' ');
    }
  }
  putchar(close);
}

void lval_print(lval* v) {
  switch (v->type) {
    case LVAL_FUN:
      if (v->builtin) {
        printf("<builtin>");
      } else {
        printf("(\\ "); lval_print(v->formals);
        putchar(' '); lval_print(v->body); putchar(')');
      }
    break;
    case LVAL_NUM:   printf("%li", v->num); break;
    case LVAL_ERR:   printf("Error: %s", v->err); break;
    case LVAL_SYM:   printf("%s", v->sym); break;
    case LVAL_SEXPR: lval_print_expr(v, '(', ')'); break;
    case LVAL_QEXPR: lval_print_expr(v, '{', '}'); break;
  }
}

void lval_println(lval* v) { lval_print(v); putchar('\n'); }

char* ltype_name(int t) {
  switch(t) {
    case LVAL_FUN: return "Function";
    case LVAL_NUM: return "Number";
    case LVAL_ERR: return "Error";
    case LVAL_SYM: return "Symbol";
    case LVAL_SEXPR: return "S-Expression";
    case LVAL_QEXPR: return "Q-Expression";
    default: return "Unknown";
  }
}

/* Lisp Environment */

struct lenv {
  lenv* par;
  int count;
  char** syms;
  lval** vals;
};

lenv* lenv_new(void) {
  lenv* e = malloc(sizeof(lenv));
  e->par = NULL;
  e->count = 0;
  e->syms = NULL;
  e->vals = NULL;
  return e;
}

void lenv_del(lenv* e) {
  for (int i = 0; i < e->count; i++) {
    free(e->syms[i]);
    lval_del(e->vals[i]);
  }  
  free(e->syms);
  free(e->vals);
  free(e);
}

lenv* lenv_copy(lenv* e) {
  lenv* n = malloc(sizeof(lenv));
  n->par = e->par;
  n->count = e->count;
  n->syms = malloc(sizeof(char*) * n->count);
  n->vals = malloc(sizeof(lval*) * n->count);
  for (int i = 0; i < e->count; i++) {
    n->syms[i] = malloc(strlen(e->syms[i]) + 1);
    strcpy(n->syms[i], e->syms[i]);
    n->vals[i] = lval_copy(e->vals[i]);
  }
  return n;
}

lval* lenv_get(lenv* e, lval* k) {
  
  for (int i = 0; i < e->count; i++) {
    if (strcmp(e->syms[i], k->sym) == 0) {
      return lval_copy(e->vals[i]);
    }
  }
  
  /* If no symbol check in parent otherwise error */
  if (e->par) {
    return lenv_get(e->par, k);
  } else {
    return lval_err("Unbound Symbol '%s'", k->sym);
  }
}

void lenv_put(lenv* e, lval* k, lval* v) {
  
  for (int i = 0; i < e->count; i++) {
    if (strcmp(e->syms[i], k->sym) == 0) {
      lval_del(e->vals[i]);
      e->vals[i] = lval_copy(v);
      return;
    }
  }

  
  e->count++;
  e->vals = realloc(e->vals, sizeof(lval*) * e->count);
  e->syms = realloc(e->syms, sizeof(char*) * e->count);  
  e->vals[e->count-1] = lval_copy(v);
  e->syms[e->count-1] = malloc(strlen(k->sym)+1);
  strcpy(e->syms[e->count-1], k->sym);
}

void lenv_def(lenv* e, lval* k, lval* v) {
  /* Iterate till e has no parent */
  while (e->par) { e = e->par; }
  /* Put value in e */
  lenv_put(e, k, v);
}

/* Builtins */

#define LASSERT(args, cond, fmt, ...) \
  if (!(cond)) { lval* err = lval_err(fmt, ##__VA_ARGS__); lval_del(args); return err; }

#define LASSERT_TYPE(func, args, index, expect) \
  LASSERT(args, args->cell[index]->type == expect, \
    "Function '%s' passed incorrect type for argument %i. " \
    "Got %s, Expected %s.", \
    func, index, ltype_name(args->cell[index]->type), ltype_name(expect))

#define LASSERT_NUM(func, args, num) \
  LASSERT(args, args->count == num, \
    "Function '%s' passed incorrect number of arguments. " \
    "Got %i, Expected %i.", \
    func, args->count, num)

#define LASSERT_NOT_EMPTY(func, args, index) \
  LASSERT(args, args->cell[index]->count != 0, \
    "Function '%s' passed {} for argument %i.", func, index);
    
lval* lval_eval(lenv* e, lval* v);

lval* builtin_lambda(lenv* e, lval* a) {
  /* Check Two arguments, each of which are Q-Expressions */
  LASSERT_NUM("\\", a, 2);
  LASSERT_TYPE("\\", a, 0, LVAL_QEXPR);
  LASSERT_TYPE("\\", a, 1, LVAL_QEXPR);
  
  /* Check first Q-Expression contains only Symbols */
  for (int i = 0; i < a->cell[0]->count; i++) {
    LASSERT(a, (a->cell[0]->cell[i]->type == LVAL_SYM),
      "Cannot define non-symbol. Got %s, Expected %s.",
      ltype_name(a->cell[0]->cell[i]->type),ltype_name(LVAL_SYM));
  }
  
  /* Pop first two arguments and pass them to lval_lambda */
  lval* formals = lval_pop(a, 0);
  lval* body = lval_pop(a, 0);
  lval_del(a);
  
  return lval_lambda(formals, body);
}

lval* builtin_list(lenv* e, lval* a) {
  a->type = LVAL_QEXPR;
  return a;
}

lval* builtin_head(lenv* e, lval* a) {
  LASSERT_NUM("head", a, 1);
  LASSERT_TYPE("head", a, 0, LVAL_QEXPR);
  LASSERT_NOT_EMPTY("head", a, 0);
  
  lval* v = lval_take(a, 0);  
  while (v->count > 1) { lval_del(lval_pop(v, 1)); }
  return v;
}

lval* builtin_tail(lenv* e, lval* a) {
  LASSERT_NUM("tail", a, 1);
  LASSERT_TYPE("tail", a, 0, LVAL_QEXPR);
  LASSERT_NOT_EMPTY("tail", a, 0);

  lval* v = lval_take(a, 0);  
  lval_del(lval_pop(v, 0));
  return v;
}

lval* builtin_eval(lenv* e, lval* a) {
  LASSERT_NUM("eval", a, 1);
  LASSERT_TYPE("eval", a, 0, LVAL_QEXPR);
  
  lval* x = lval_take(a, 0);
  x->type = LVAL_SEXPR;
  return lval_eval(e, x);
}

lval* builtin_join(lenv* e, lval* a) {
  
  for (int i = 0; i < a->count; i++) {
    LASSERT_TYPE("join", a, i, LVAL_QEXPR);
  }
  
  lval* x = lval_pop(a, 0);
  
  while (a->count) {
    lval* y = lval_pop(a, 0);
    x = lval_join(x, y);
  }
  
  lval_del(a);
  return x;
}

lval* builtin_op(lenv* e, lval* a, char* op) {
  
  for (int i = 0; i < a->count; i++) {
    LASSERT_TYPE(op, a, i, LVAL_NUM);
  }
  
  lval* x = lval_pop(a, 0);
  
  if ((strcmp(op, "-") == 0) && a->count == 0) { x->num = -x->num; }
  
  while (a->count > 0) {  
    lval* y = lval_pop(a, 0);
    
    if (strcmp(op, "+") == 0) { x->num += y->num; }
    if (strcmp(op, "-") == 0) { x->num -= y->num; }
    if (strcmp(op, "*") == 0) { x->num *= y->num; }
    if (strcmp(op, "/") == 0) {
      if (y->num == 0) {
        lval_del(x); lval_del(y);
        x = lval_err("Division By Zero.");
        break;
      }
      x->num /= y->num;
    }
    
    lval_del(y);
  }
  
  lval_del(a);
  return x;
}

lval* builtin_add(lenv* e, lval* a) { return builtin_op(e, a, "+"); }
lval* builtin_sub(lenv* e, lval* a) { return builtin_op(e, a, "-"); }
lval* builtin_mul(lenv* e, lval* a) { return builtin_op(e, a, "*"); }
lval* builtin_div(lenv* e, lval* a) { return builtin_op(e, a, "/"); }

lval* builtin_var(lenv* e, lval* a, char* func) {
  LASSERT_TYPE(func, a, 0, LVAL_QEXPR);
  
  lval* syms = a->cell[0];
  for (int i = 0; i < syms->count; i++) {
    LASSERT(a, (syms->cell[i]->type == LVAL_SYM),
      "Function '%s' cannot define non-symbol. "
      "Got %s, Expected %s.", func, 
      ltype_name(syms->cell[i]->type), ltype_name(LVAL_SYM));
  }
  
  LASSERT(a, (syms->count == a->count-1),
    "Function '%s' passed too many arguments for symbols. "
    "Got %i, Expected %i.", func, syms->count, a->count-1);
    
  for (int i = 0; i < syms->count; i++) {
    /* If 'def' define in globally. If 'put' define in locally */
    if (strcmp(func, "def") == 0) {
      lenv_def(e, syms->cell[i], a->cell[i+1]);
    }
    
    if (strcmp(func, "=")   == 0) {
      lenv_put(e, syms->cell[i], a->cell[i+1]);
    } 
  }
  
  lval_del(a);
  return lval_sexpr();
}

lval* builtin_def(lenv* e, lval* a) {
  return builtin_var(e, a, "def");
}

lval* builtin_put(lenv* e, lval* a) {
  return builtin_var(e, a, "=");
}

void lenv_add_builtin(lenv* e, char* name, lbuiltin func) {
  lval* k = lval_sym(name);
  lval* v = lval_builtin(func);
  lenv_put(e, k, v);
  lval_del(k); lval_del(v);
}

void lenv_add_builtins(lenv* e) {
  /* Variable Functions */
  lenv_add_builtin(e, "\\",  builtin_lambda); 
  lenv_add_builtin(e, "def", builtin_def);
  lenv_add_builtin(e, "=",   builtin_put);
  
  /* List Functions */
  lenv_add_builtin(e, "list", builtin_list);
  lenv_add_builtin(e, "head", builtin_head);
  lenv_add_builtin(e, "tail", builtin_tail);
  lenv_add_builtin(e, "eval", builtin_eval);
  lenv_add_builtin(e, "join", builtin_join);
  
  /* Mathematical Functions */
  lenv_add_builtin(e, "+", builtin_add);
  lenv_add_builtin(e, "-", builtin_sub);
  lenv_add_builtin(e, "*", builtin_mul);
  lenv_add_builtin(e, "/", builtin_div);
}

/* Evaluation */

lval* lval_call(lenv* e, lval* f, lval* a) {
  
  /* If Builtin then simply apply that */
  if (f->builtin) { return f->builtin(e, a); }
  
  /* Record Argument Counts */
  int given = a->count;
  int total = f->formals->count;
  
  /* While arguments still remain to be processed */
  while (a->count) {
    
    /* If we've ran out of formal arguments to bind */
    if (f->formals->count == 0) {
      lval_del(a);
      return lval_err("Function passed too many arguments. "
        "Got %i, Expected %i.", given, total); 
    }
    
    /* Pop the first symbol from the formals */
    lval* sym = lval_pop(f->formals, 0);
    
    /* Special Case to deal with '&' */
    if (strcmp(sym->sym, "&") == 0) {
      
      /* Ensure '&' is followed by another symbol */
      if (f->formals->count != 1) {
        lval_del(a);
        return lval_err("Function format invalid. "
          "Symbol '&' not followed by single symbol.");
      }
      
      /* Next formal should be bound to remaining arguments */
      lval* nsym = lval_pop(f->formals, 0);
      lenv_put(f->env, nsym, builtin_list(e, a));
      lval_del(sym); lval_del(nsym);
      break;
    }
    
    /* Pop the next argument from the list */
    lval* val = lval_pop(a, 0);
    
    /* Bind a copy into the function's environment */
    lenv_put(f->env, sym, val);
    
    /* Delete symbol and value */
    lval_del(sym); lval_del(val);
  }
  
  /* Argument list is now bound so can be cleaned up */
  lval_del(a);
  
  /* If '&' remains in formal list bind to empty list */
  if (f->formals->count > 0 &&
    strcmp(f->formals->cell[0]->sym, "&") == 0) {
    
    /* Check to ensure that & is not passed invalidly. */
    if (f->formals->count != 2) {
      return lval_err("Function format invalid. "
        "Symbol '&' not followed by single symbol.");
    }
    
    /* Pop and delete '&' symbol */
    lval_del(lval_pop(f->formals, 0));
    
    /* Pop next symbol and create empty list */
    lval* sym = lval_pop(f->formals, 0);
    lval* val = lval_qexpr();
    
    /* Bind to environment and delete */
    lenv_put(f->env, sym, val);
    lval_del(sym); lval_del(val);
  }
  
  /* If all formals have been bound evaluate */
  if (f->formals->count == 0) {
  
    /* Set environment parent to evaluation environment */
    f->env->par = e;
    
    /* Evaluate and return */
    return builtin_eval(f->env, 
      lval_add(lval_sexpr(), lval_copy(f->body)));
  } else {
    /* Otherwise return partially evaluated function */
    return lval_copy(f);
  }
  
}

lval* lval_eval_sexpr(lenv* e, lval* v) {
  
  for (int i = 0; i < v->count; i++) {
    v->cell[i] = lval_eval(e, v->cell[i]);
  }
  
  for (int i = 0; i < v->count; i++) {
    if (v->cell[i]->type == LVAL_ERR) { return lval_take(v, i); }
  }
  
  if (v->count == 0) { return v; }  
  if (v->count == 1) { return lval_eval(e, lval_take(v, 0)); }
  
  lval* f = lval_pop(v, 0);
  if (f->type != LVAL_FUN) {
    lval* err = lval_err(
      "S-Expression starts with incorrect type. "
      "Got %s, Expected %s.",
      ltype_name(f->type), ltype_name(LVAL_FUN));
    lval_del(f); lval_del(v);
    return err;
  }
  
  lval* result = lval_call(e, f, v);
  lval_del(f);
  return result;
}

lval* lval_eval(lenv* e, lval* v) {
  if (v->type == LVAL_SYM) {
    lval* x = lenv_get(e, v);
    lval_del(v);
    return x;
  }
  if (v->type == LVAL_SEXPR) { return lval_eval_sexpr(e, v); }
  return v;
}

/* Reading */

lval* lval_read_num(mpc_ast_t* t) {
  errno = 0;
  long x = strtol(t->contents, NULL, 10);
  return errno != ERANGE ? lval_num(x) : lval_err("Invalid Number.");
}

lval* lval_read(mpc_ast_t* t) {
  
  if (strstr(t->tag, "number")) { return lval_read_num(t); }
  if (strstr(t->tag, "symbol")) { return lval_sym(t->contents); }
  
  lval* x = NULL;
  if (strcmp(t->tag, ">") == 0) { x = lval_sexpr(); } 
  if (strstr(t->tag, "sexpr"))  { x = lval_sexpr(); }
  if (strstr(t->tag, "qexpr"))  { x = lval_qexpr(); }
  
  for (int i = 0; i < t->children_num; i++) {
    if (strcmp(t->children[i]->contents, "(") == 0) { continue; }
    if (strcmp(t->children[i]->contents, ")") == 0) { continue; }
    if (strcmp(t->children[i]->contents, "}") == 0) { continue; }
    if (strcmp(t->children[i]->contents, "{") == 0) { continue; }
    if (strcmp(t->children[i]->tag,  "regex") == 0) { continue; }
    x = lval_add(x, lval_read(t->children[i]));
  }
  
  return x;
}

/* Main */

int main(int argc, char** argv) {
  
  mpc_parser_t* Number = mpc_new("number");
  mpc_parser_t* Symbol = mpc_new("symbol");
  mpc_parser_t* Sexpr  = mpc_new("sexpr");
  mpc_parser_t* Qexpr  = mpc_new("qexpr");
  mpc_parser_t* Expr   = mpc_new("expr");
  mpc_parser_t* Lispy  = mpc_new("lispy");
  
  mpca_lang(MPCA_LANG_DEFAULT,
    "                                                     \
      number : /-?[0-9]+/ ;                               \
      symbol  : /[a-zA-Z0-9_+\\-*\\/\\\\=<>!&]+/ ;        \
      sexpr  : '(' <expr>* ')' ;                          \
      qexpr  : '{' <expr>* '}' ;                          \
      expr   : <number> | <symbol> | <sexpr> | <qexpr> ;  \
      lispy  : /^/ <expr>* /$/ ;                          \
    ",
    Number, Symbol, Sexpr, Qexpr, Expr, Lispy);
  
  puts("Lispy Version 0.0.0.0.8");
  puts("Press Ctrl+c to Exit\n");
  
  lenv* e = lenv_new();
  lenv_add_builtins(e);
  
  while (1) {
  
    char* input = readline("lispy> ");
    add_history(input);
    
    mpc_result_t r;
    if (mpc_parse("<stdin>", input, Lispy, &r)) {
      lval* x = lval_eval(e, lval_read(r.output));
      lval_println(x);
      lval_del(x);
      mpc_ast_delete(r.output);
    } else {    
      mpc_err_print(r.error);
      mpc_err_delete(r.error);
    }
    
    free(input);
    
  }
  
  lenv_del(e);
  
  mpc_cleanup(6, Number, Symbol, Sexpr, Qexpr, Expr, Lispy);
  
  return 0;
}

Metas Bônus


  • › Defina uma função Lisp que devolva o primeiro elemento duma lista.
  • › Defina uma função Lisp que devolva o segundo elemento duma lista.
  • › Defina uma função Lisp que chame uma função com dois argumentos na ordem inversa.
  • › Defina uma função Lisp que chame uma função com argumentos, e depois passe o resultado para uma outra função.
  • › Mude os argumentos variáveis para que pelo menos um argumento extra precise ser fornecido para que seja avaliado.

Navegação

‹ Variáveis

• Conteúdo •

Condicionais ›