Lisp com sabor Pascal ou a linguagem de programação 8501st

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 compartilhadas
Um 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 encapsulamento
Embora 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órias
Qual é 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. Multithreading
Essa 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 ponteiros
Na 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ódulos
Como 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 -) ;  «-.lisya»    

 (use -) ;,       


3. Menos abreviações
Os 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úsculas
Acredito 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 russo
Sintaxe 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 estendido
Uma 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éricos
Esta é a extensão de idioma mais útil para mim.

 10_000 ;    10k ;       10 ;       10° 10pi 10deg 10 ;   10π ; pi     10+i10 ;   10+10 ;    1010deg ;          

A última opção me parece a mais não estética, mas é a mais popular.

8. Ciclos
Os 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 ;   i = 0..4 ) (for i 1..6 ;   i = 1..5 ) (for i  ;     ;      ) (for i (subseq  2) ;           ) 

A variável de loop não é visível do lado de fora.

 (while  ) 

9. GOTO
Nã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 escopo
Existem 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 único
No 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 conveniente
O 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-rotina
Existem 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. Multithreading
A 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.

 ;   (var  (thread --1)) (+ (--2) (wait )) 

15. Limpeza funcional no código imperativo
O 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 generalizados
Capacidade de sobrecarregar funções com defgeneric / defmethod.

2. Herança

3. Depurador embutido
Quando ocorre uma exceção, o interpretador Lisp alterna para o modo de depuração.

4. UFFI
Interface para conectar módulos escritos em outros idiomas.

5. BIGNUM
Suporte de profundidade de bits arbitrário

Descartado

Alguns dos recursos do Lisp foram considerados e considerados inúteis / prejudiciais.

1. Combinação guiada de métodos
Quando 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. Reinicia
O 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 romano
O 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) 

 ;     (map (function (x) (** x 2)) \(1 2 3)) 

 ;   ,   qwe      (filter (function (x) (regexp:match x «^qwe...»)) -) ;   ,   ,    (filter-th (function (x) (regexp:match x «^qwe...»)) -) 

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 aqui

O 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. Console

2. editor de texto
Equipado 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.

Source: https://habr.com/ru/post/pt424447/


All Articles