Há algum tempo (cerca de três anos), decidi ler um livro sobre Lisp. Sem qualquer objetivo específico, apenas por uma questão de desenvolvimento geral e a capacidade de chocar os interlocutores com exóticos (quando parece, até funcionou).
Porém, após uma inspeção mais minuciosa, Lisp se mostrou realmente poderoso, flexível e, curiosamente, útil na vida cotidiana. Todas as pequenas tarefas de automação migraram rapidamente para scripts no Lisp, e também houve oportunidades para automação de tarefas mais complexas.
Deve-se notar aqui que, por “capacidade de automação”, quero dizer uma situação em que o tempo total para gravar e depurar um programa é menor que o tempo gasto na solução manual da mesma tarefa.
Paul Graham escreveu mais de um artigo e até um livro sobre os benefícios do Lisp. No momento em que este artigo foi escrito, Lisp ficou em 33º lugar no ranking TOIBE (três vezes mais morto que Delphi morto). Surge a pergunta: por que a linguagem é tão pequena se é tão conveniente? Cerca de dois anos de uso deram algumas dicas dos motivos.
Desvantagens
1. Estruturas de dados compartilhadasUm conceito que permite otimizar programas funcionais, mas repleto de erros sutis em imperativo. A possibilidade de danos acidentais a uma estrutura de dados estranhos quando uma variável é modificada que não possui uma conexão visível com a estrutura exige que o programador monitore constantemente o que está acontecendo nos bastidores e conheça a implementação interna de cada função usada (sistema e usuário). O mais surpreendente é a capacidade de danificar o corpo de uma função modificando seu valor de retorno.
2. Falta de encapsulamentoEmbora o conceito de pacote exista, ele não tem nada a ver com
pacote no Ada ou
unidade no Delphi. Qualquer código pode adicionar qualquer coisa a qualquer pacote (exceto os do sistema). Qualquer código pode extrair qualquer coisa de qualquer pacote usando o operador
:: .
3. Abreviações aleatóriasQual é a diferença entre MAPCAN e MAPCON? Por que no SETQ, a última letra Q? Dada a idade do idioma, você pode entender as razões desse estado de coisas, mas eu quero que o idioma seja um pouco mais limpo.
4. MultithreadingEssa desvantagem está indiretamente relacionada ao Lisp e diz respeito principalmente à implementação que eu uso - o SteelBank Common Lisp. O Lisp comum não suporta multithreading. Uma tentativa de usar a implementação fornecida pelo SBCL falhou.
É uma pena recusar uma ferramenta tão conveniente, mas a insatisfação gradualmente se acumula.
Procure uma solução
Primeiro você pode ir à Wikipedia na página Lisp. Inspecione a seção "Dialetos". Leia uma breve introdução a cada um. E perceba que o sabor e a cor de todos os marcadores são diferentes.
Se você quer fazer algo, precisa fazer você mesmo
- Jean Baptiste Emmanuel Sorg
Vamos tentar criar nosso próprio Lisp correto, adicionando um pouco de Ada, muito Delphi e uma gota de Oberon. Chamamos a mistura resultante de Fox.
Conceitos básicos
1. Sem ponteirosNa luta contra o PROBLEM-1, todas as operações devem ser realizadas copiando os valores. Pelo tipo de estrutura de dados no código ou na impressão, todas as suas propriedades, conexões externas e internas devem estar totalmente visíveis.
2. Adicione módulosComo parte da luta contra o problema-2, importamos
pacotes ,
com e
usamos declarações da Ada. No processo, descartamos o esquema de importação / sombreamento excessivamente complexo para símbolos Lisp.
(package - ( ) () ())
(with -)
(use -)
3. Menos abreviaçõesOs caracteres mais comuns e comuns ainda serão abreviados, mas principalmente os mais óbvios:
const ,
var . Função de saída formatada - o FMT requer redução, pois geralmente é encontrado em expressões.
Elt - pegando um elemento - vazou do Common Lisp e criou raízes, embora não seja necessário reduzi-lo.
4. Identificadores que não diferenciam maiúsculas de minúsculasAcredito que o idioma correto (e o sistema de arquivos) {$ HOLYWAR +} não faça distinção entre maiúsculas e minúsculas {$ HOLYWAR-}, de modo a não quebrar a cabeça novamente.
5. Facilidade de uso com o layout do teclado russoSintaxe Lisi de todas as maneiras possíveis, evita o uso de caracteres que não estão disponíveis em um dos layouts. Sem chaves quadradas ou encaracoladas. Não #, ~, &, <,>, |. Ao ler literais numéricos, uma vírgula e um ponto são considerados separadores decimais.
6. Alfabeto estendidoUma das coisas legais da SBCL era o código UTF-8. A capacidade de declarar as constantes BEAR, VODKA e BALALAYKA simplifica muito a escrita do código do aplicativo. A capacidade de inserir Ω, Ψ e Σ torna as fórmulas no código mais visuais. Embora teoricamente exista a possibilidade de usar caracteres Unicode, é difícil garantir a correção de trabalhar com eles (mais preguiça do que difícil). Nos limitamos a cirílico, latim e grego.
7. Literais numéricosEsta é a extensão de idioma mais útil para mim.
10_000
A última opção me parece a mais não estética, mas é a mais popular.
8. CiclosOs ciclos no Lisp não são padrão e são bastante confusos. Simplifique para o conjunto padrão mínimo.
(for i 5
A variável de loop não é visível do lado de fora.
(while )
9. GOTONão é um operador muito necessário, mas sem ele é difícil demonstrar negligência das regras da programação estrutural.
(block : (goto :))
10. Unificação do escopoExistem dois tipos diferentes de escopo no Lisp: TOPLEVEL e local. Consequentemente, existem duas maneiras diferentes de declarar variáveis.
(defvar A 1) (let ((a 1)) …)
No Fox, existe apenas um método usado no nível superior do script e em áreas locais, incluindo pacotes.
(var A 1)
Se você deseja limitar o escopo, use o operador
(block (var A 1) (set A 2) (fmt nil A))
O corpo do loop está contido na instrução BLOCK implícita (como o corpo da função / procedimento). Todas as variáveis declaradas no loop são destruídas no final da iteração.
11. Caracteres de slot únicoNo Lisp, funções são objetos especiais e são armazenadas em um slot de símbolo especial. Um único caractere pode armazenar simultaneamente uma variável, função e lista de propriedades. Em uma raposa, cada personagem está associado a apenas um significado.
12. ELT convenienteO acesso típico a um elemento de estrutura complexa no Lisp se parece com isso
(elt (slot-value (elt 1) '-2) 3)
O Fox implementa um operador ELT unificado que fornece acesso a elementos de qualquer tipo composto (listas, seqüências de caracteres, registros, matrizes de bytes, tabelas de hash).
(elt 1 \-2 3)
A funcionalidade idêntica também pode ser obtida com uma macro no Lisp
(defmacro field (object &rest f) " . (field *object* 0 :keyword symbol \"string\") . plist. ( ) . ." (if f (symbol-macrolet ((f0 (elt f 0))(rest (subseq f 1))) (cond ((numberp f0) `(field (elt ,object ,f0) ,@rest)) ((keywordp f0) `(field (getf ,object ,f0) ,@rest)) ((stringp f0) `(field (cdr (assoc ,f0 ,object :test 'equal)) ,@rest)) ((and (listp f0) (= 2 (length f0))) `(field (,(car f0) ,(cadr f0) ,object) ,@rest)) ((symbolp f0) `(field (,f0 ,object) ,@rest)) (t `(error " ")))) object))
13. Restrição dos modos de transferência de parâmetro de sub-rotinaExistem pelo menos cinco modos de transferência de parâmetros no Lisp: obrigatório,
& opcional ,
& rest ,
& key ,
& whole e sua combinação arbitrária é permitida. De fato, a maioria das combinações produz efeitos estranhos.
No Fox, é permitido usar apenas uma combinação dos parâmetros necessários e um dos seguintes modos para escolher
: key ,: optional ,: flag ,: rest .
14. MultithreadingA fim de simplificar ao máximo a gravação de programas multithread, o conceito de separação de memória foi adotado. Quando um encadeamento é gerado, todas as variáveis disponíveis para o novo encadeamento são copiadas. Todas as referências a essas variáveis são substituídas por referências a cópias. A transferência de informações entre fluxos é possível apenas por meio de objetos protegidos ou pelo resultado retornado pelo fluxo após a conclusão.
Os objetos protegidos sempre contêm seções críticas para garantir operações atômicas. O login em seções críticas é automático - não há operadores separados para isso no idioma. Os objetos protegidos incluem: fila de mensagens, console e descritores de arquivo.
É possível criar threads com exibição multiencadeada
(map-th (function (x) …) --)
O Map-th inicia automaticamente o número de threads igual ao número de processadores no sistema (ou o dobro se houver Intel dentro). Em uma chamada recursiva, as chamadas subsequentes de mapeamento th funcionam em um único encadeamento.
Além disso, há uma função de thread interna que executa um procedimento / função em um thread separado.
15. Limpeza funcional no código imperativoO Fox possui funções para programação funcional e procedimentos para procedimentos. As rotinas declaradas usando a palavra-chave function estão sujeitas aos requisitos da ausência de efeitos colaterais e da independência do resultado de fatores externos.
Não realizado
Algumas características interessantes do Lisp permaneceram não preenchidas devido à baixa prioridade.
1. Métodos generalizadosCapacidade de sobrecarregar funções com defgeneric / defmethod.
2. Herança3. Depurador embutidoQuando ocorre uma exceção, o interpretador Lisp alterna para o modo de depuração.
4. UFFIInterface para conectar módulos escritos em outros idiomas.
5. BIGNUMSuporte de profundidade de bits arbitrário
DescartadoAlguns dos recursos do Lisp foram considerados e considerados inúteis / prejudiciais.
1. Combinação guiada de métodosQuando um método é chamado para uma classe, uma combinação de métodos pai é executada e é possível alterar as regras de combinação. O comportamento final do método parece pouco previsível.
2. ReiniciaO manipulador de exceção pode fazer alterações no estado do programa e enviar um comando de reinicialização para o código que gerou a exceção. O efeito do aplicativo é semelhante ao uso do operador GOTO para alternar de função para função.
3. O relato romanoO Lisp suporta o sistema numérico, que está desatualizado pouco antes de sua aparência.
Use
Aqui estão alguns exemplos de código simples.
(function crc8 (data :optional seed) (var result (if-nil seed 0)) (var s_data data) (for bit 8 (if (= (bit-and (bit-xor result s_data) $01) 0) (set result (shift result -1 8)) (else (set result (bit-xor result $18)) (set result (shift result -1 8)) (set result (bit-or result $80)))) (set s_data (shift s_data -1 8))) result)
Implementação
O intérprete é escrito em Delphi (FreePascal no modo de compatibilidade). É construído no Lazarus 1.6.2 e superior, no Windows e Linux de 32 e 64 bits. Das dependências externas, requer libmysql.dll. Contém cerca de 15_000..20_000 linhas. Existem cerca de 200 funções internas para vários propósitos (algumas são sobrecarregadas oito vezes).
Armazenado aquiO suporte à digitação dinâmica é realizado de maneira trivial - todos os tipos de dados processados são representados pelos herdeiros da mesma classe TValue.
O tipo mais importante para o Lisp - a lista é, como é habitual no Delphi, uma classe que contém uma matriz dinâmica de objetos do tipo TValue. Para esse tipo, o mecanismo CopyOnWrite é implementado.
O gerenciamento de memória é automático com base na contagem de referência. Para estruturas recursivas, todos os links na estrutura são contados simultaneamente. A liberação da memória é iniciada imediatamente quando as variáveis saem do escopo. Não há mecanismos para o início atrasado do coletor de lixo.
O tratamento de exceções funciona em um mecanismo embutido no Delphi. Assim, os erros que ocorrem no código do intérprete podem ser processados pelo código executável no Fox.
Cada operador ou função Lisi interna é implementada como um método ou função no código do intérprete. O script é executado por chamada de implementações mutuamente recursivas. O código do interpretador e o script têm uma pilha de chamadas comum.
As variáveis de script são armazenadas na memória dinâmica de forma independente. Cada função definida pelo usuário possui sua própria pilha para armazenar referências de variáveis, independentemente da pilha de nível superior ou da pilha de funções-pai.
De particular dificuldade é a implementação do operador de atribuição (conjunto) para elementos estruturais. O cálculo direto do ponteiro para o elemento requerido leva ao risco de dangling links, uma vez que a sintaxe Lisi não proíbe modificar a estrutura durante o cálculo do elemento requerido. Como solução de compromisso, um “ponteiro de cadeia” é implementado - um objeto que contém uma referência a uma variável e uma matriz de índices numéricos para indicar o caminho dentro da estrutura. Esse ponteiro também é suscetível ao problema de links danificados, mas no caso de uma falha, ele gera uma mensagem de erro significativa.
Ferramentas de desenvolvimento
1. Console2. editor de textoEquipado com destaque de sintaxe e a capacidade de executar um script editável no F9.

Conclusão
No estado atual, o projeto resolve os problemas para os quais foi concebido e não requer desenvolvimento ativo adicional. Muitas das imperfeições presentes não afetam significativamente o trabalho.