Depois de alguns meses aprendendo e brincando com Lisp, CL e um pouco de Clojure, ainda não vejo uma razão convincente para escrever algo nele em vez de C #.
Eu realmente gostaria de algumas razões convincentes, ou para alguém ressaltar que estou sentindo falta de algo realmente grande .
Os pontos fortes do Lisp (de acordo com minha pesquisa):
- Notação compacta e expressiva - Mais do que C #, sim ... mas também consigo expressar essas idéias em C #.
- Suporte implícito à programação funcional - C # com métodos de extensão LINQ:
- mapcar = .Selecione (lambda)
- Mapcan = .Selecione (lambda) .Aggregate ((a, b) => a.União (b))
- carro / primeiro = .Primeiro ()
- cdr / rest = .Skip (1) .... etc.
- Suporte a funções Lambda e de ordem superior - C # possui isso, e a sintaxe é sem dúvida mais simples:
- "(lambda (x) (corpo))" versus "x => (corpo)"
- "# (" com "%", "% 1", "% 2" é bom no Clojure
- Envio de método separado dos objetos - o C # tem isso através de métodos de extensão
- Despacho multimétodo - o C # não possui isso nativamente, mas eu poderia implementá-lo como uma chamada de função em algumas horas
- Código é dados (e macros) - Talvez eu não tenha "recebido" macros, mas não vi um único exemplo em que a idéia de uma macro não pudesse ser implementada como uma função; isso não muda a "linguagem", mas não tenho certeza se isso é uma força
- DSLs - Só pode fazê-lo através da composição de funções ... mas funciona
- Programação "exploratória" não tipada - para estruturas / classes, as propriedades automáticas do C # e o "objeto" funcionam muito bem, e você pode escalar facilmente para uma digitação mais forte à medida que avança
- É executado em hardware que não é Windows - Sim, é? Fora da faculdade, conheci apenas uma pessoa que não executa o Windows em casa ou pelo menos uma VM do Windows no * nix / Mac. (Então, novamente, talvez isso seja mais importante do que eu pensava e acabei de fazer uma lavagem cerebral ...)
- O REPL para o projeto de baixo para cima - Ok, admito que isso é realmente muito bom e sinto falta dele em C #.
Coisas que estou perdendo no Lisp (devido a uma mistura de C #, .NET, Visual Studio, Resharper):
- Namespaces. Mesmo com métodos estáticos, eu gosto de amarrá-los a uma "classe" para categorizar seu contexto (Clojure parece ter isso, CL não parece).
- Excelente compilação e suporte em tempo de design
- o sistema de tipos permite determinar a "correção" das estruturas de dados que eu passo
- qualquer erro de ortografia é sublinhado em tempo real; Eu não tenho que esperar até o tempo de execução para saber
- melhorias de código (como usar uma abordagem FP em vez de uma abordagem imperativa) são sugeridas automaticamente
- Ferramentas de desenvolvimento da GUI: WinForms e WPF (sei que o Clojure tem acesso às bibliotecas da GUI do Java, mas são totalmente estranhas para mim).
- Ferramentas de depuração da GUI: pontos de interrupção, entrada e saída, inspetores de valor (texto, xml, personalizado), relógios, depuração por segmento, pontos de interrupção condicionais, janela de pilha de chamadas com a capacidade de ir para o código em qualquer nível na pilha
- (Para ser justo, minha passagem pelo Emacs + Slime parecia fornecer parte disso, mas sou parcial com a abordagem orientada a VS GUI)
Eu realmente gosto do hype em torno de Lisp e dei uma chance.
Mas há algo que eu possa fazer no Lisp que não possa fazer tão bem em C #? Pode ser um pouco mais detalhado em C #, mas também tenho preenchimento automático.
o que estou perdendo? Por que devo usar o Clojure / CL?
AND
ouOR
como uma função. Isso poderia ser feito (dadoLAMBDA
, o que também é uma macro), mas não vejo uma maneira óbvia de fazê-lo que não seja totalmente ruim.:
(ou::
para símbolos não exportados) para nomear símbolos em pacotes, por exemplo, seu exemplo aqui usariahttp:session
echat:session
.Respostas:
As macros permitem escrever compiladores sem deixar o conforto do seu idioma. DSLs em idiomas como C # geralmente equivalem a um intérprete de tempo de execução. Dependendo do seu domínio, isso pode ser problemático por razões de desempenho. A velocidade importa , caso contrário, você codificaria em uma linguagem interpretada e não em C #.
Além disso, as macros também permitem considerar fatores humanos - qual é a sintaxe mais amigável para uma DSL específica? Com o C #, não há muito o que fazer sobre a sintaxe.
Portanto, o Lisp permite que você participe do design da linguagem sem sacrificar o caminho para a eficiência . E, embora esses problemas possam não ser importantes para você , eles são extremamente importantes, pois estão na base de todas as linguagens de programação úteis orientadas à produção. Em C #, esse elemento fundamental é exposto a você de uma forma muito limitada, na melhor das hipóteses.
A importância das macros não se perde em outros idiomas - o modelo Haskell, camlp4 vem à mente. Mas, novamente, é uma questão de usabilidade - as macros Lisp até o momento são provavelmente a implementação mais amigável e poderosa da transformação em tempo de compilação.
Então, em resumo, o que as pessoas geralmente fazem com idiomas como C # é criar DSILs (Domínios Interpretados Específicos de Domínio). O Lisp oferece a oportunidade de criar algo muito mais radical, DSPs (Domain Specific Paradigms). O Racket (anteriormente PLT-Scheme) é particularmente inspirador nesse sentido - eles têm uma linguagem preguiçosa (Lazy Racket), um tipo digitado (Typed Racket), Prolog e Datalog, todos incorporados linguisticamente por macros. Todos esses paradigmas fornecem soluções poderosas para grandes classes de problemas - problemas que não são melhor resolvidos por programação imperativa ou mesmo paradigmas de PF.
fonte
Quase tudo o que estava originalmente disponível apenas no Lisps, felizmente, migrou para muitas outras línguas modernas. A maior exceção é a filosofia "código é dados" e macros.
Sim, é difícil criar macros. A metaprogramação em geral é difícil de entender. Se você viu alguma macro que pudesse ser implementada como uma função, o programador estava fazendo errado. As macros devem ser usadas apenas quando uma função não funcionar.
O exemplo "olá mundo" de uma macro é escrever "se" em termos de cond. Se você estiver realmente interessado em aprender o poder das macros, tente definir um "my-if" no clojure, usando funções e observe como ele se quebra. Não pretendo ser misterioso, nunca vi alguém começar a entender macros apenas por explicação, mas esta apresentação de slides é uma introdução muito boa: http://www.slideshare.net/pcalcado/lisp-macros-in -20-minutos-com-apresentação-clojure
Eu tive pequenos projetos nos quais salvei literalmente centenas / milhares de linhas de código usando macros (em comparação com o que seria necessário em Java). Grande parte do Clojure é implementada no Clojure, o que só é possível por causa do sistema macro.
fonte
(defun my-if (test then &optional else) (cond (test (funcall then)) ('otherwise (funcall else))))
Não sou especialista em Common Lisp, mas conheço o C # e o Clojure razoavelmente bem; espero que essa seja uma perspectiva útil.
Como resultado da sintaxe incrivelmente simples, a filosofia de "código é dados" de Lisp é muito mais poderosa do que qualquer coisa que você possa fazer com composição de funções / modelos / genéricos etc. É mais do que apenas DSLs, é geração e manipulação de código, em tempo de compilação , como uma característica fundamental da linguagem. Se você tentar obter esse tipo de recursos em uma linguagem não homoicônica como C # ou Java, acabará reinventando muito o Lisp. Daí a famosa Décima Regra de Greenspuns: "Qualquer programa C ou Fortran suficientemente complicado contém uma implementação lenta ad hoc, especificada informalmente, cheia de bugs e com erros, de metade do Common Lisp". Se você já inventou uma linguagem de controle de fluxo / modelagem completa em seu aplicativo, provavelmente já sabe o que quero dizer .....
A simultaneidade é uma força única do Clojure (mesmo em relação a outros Lisps), mas se você acredita que o futuro da programação significará ter que escrever aplicativos que escalam para máquinas com vários núcleos, então você realmente deve analisar isso - é bastante inovador. O Clojure STM (Memória Transacional de Software) funciona porque o Clojure adota imutabilidade e construções de simultaneidade sem bloqueio com base em uma nova separação de identidade e estado (consulte o excelente vídeo de Rich Hickey sobre identidade e estado ). Seria muito doloroso enxertar esse tipo de capacidade de simultaneidade em um ambiente de idioma / tempo de execução em que uma proporção significativa das APIs trabalha em objetos mutáveis.
Programação funcional - os Lisps enfatizam a programação com funções de ordem superior. Você está certo de que ele pode ser emulado em objetos OO com objetos de fechamento / função etc., mas a diferença é que todo o idioma e a biblioteca padrão foram projetados para ajudá-lo a escrever código dessa maneira. Por exemplo, as sequências do Clojure são preguiçosas , portanto podem ser infinitamente longas, diferentemente de ArrayLists ou HashMaps em C # / Java. Isso faz com que alguns algoritmos muito mais fácil de expressar, por exemplo, os seguintes implementos uma lista infinita de números de Fibonacci:
(def fibs (lazy-cat '(0 1) (map + fibs (drop 1 fibs))))
Digitação dinâmica - os Lisps tendem a estar no final "dinâmico" do espectro da linguagem de programação funcional (em oposição a idiomas como Haskell, com uma concepção muito forte e bonita de tipos estáticos). Isso tem vantagens e desvantagens, mas eu argumentaria que as linguagens dinâmicas tendem a favorecer a produtividade da programação devido à maior flexibilidade. Essa digitação dinâmica tem um custo menor de desempenho em tempo de execução, mas se isso o incomoda, você sempre pode adicionar dicas de tipo ao seu código para obter digitação estática. Basicamente - você obtém conveniência por padrão e desempenho, se estiver disposto a fazer um pouco de trabalho extra para obtê-lo.
Desenvolvimento interativo - na verdade, acho que você pode obter algum tipo de REPL para C # 4.0, mas no Lisp é uma prática padrão e não uma novidade. Você não precisa executar um ciclo de compilação / construção / teste - você escreve seu código diretamente no ambiente em execução e apenas o copia posteriormente nos arquivos de origem. Ele ensina uma maneira muito diferente de codificar, onde você vê resultados imediatamente e refina sua solução de forma iterativa.
Alguns comentários rápidos sobre o que você vê como "ausente":
Pessoalmente, considero as vantagens do Clojure / Lisp bastante atraentes e não pretendo voltar ao C # / Java (além do que eu preciso emprestar todas as bibliotecas úteis!).
No entanto, demorou alguns meses para eu realmente entender tudo. Se você está realmente interessado em aprender o Lisp / Clojure como uma experiência de aprendizado esclarecedora, não há substituto para mergulhar e se forçar a implementar algumas coisas ...
fonte
O C # possui muitos recursos, mas o mix parece diferente. O fato de alguma linguagem ter uma característica ainda não significa que seja fácil de usar, paradigmático e bem integrado ao restante da linguagem.
Lisp comum tem esta mistura:
Agora, outros idiomas, como C #, também têm isso. Mas muitas vezes não com a mesma ênfase.
C # tem
Não ajuda que o C # tenha, por exemplo, recursos de manipulação de código, se não forem amplamente utilizados como no Lisp. No Lisp, você não encontrará muitos softwares que não fazem uso pesado de macros de todas as formas simples e complexas. Usar manipulação de código é realmente um grande recurso no Lisp. Emular alguns usos é possível em muitos idiomas, mas não o torna um recurso central como no Lisp. Veja livros como 'On Lisp' ou 'Practical Common Lisp' para exemplos.
O mesmo para desenvolvimento interativo. Se você desenvolver um grande sistema CAD, a maior parte do desenvolvimento será interativa a partir do editor e do REPL - diretamente no aplicativo CAD em execução. Você pode emular muito disso em C # ou em outros idiomas - geralmente implementando um idioma de extensão. Para Lisp, seria estúpido reiniciar o aplicativo CAD em desenvolvimento para adicionar alterações. O sistema CAD também conteria um Lisp aprimorado que incluiria recursos de linguagem (na forma de macros e descrições simbólicas) para descrever objetos CAD e seus relacionamentos (parte de, contido, ...). Pode-se fazer coisas semelhantes em C #, mas no Lisp esse é o estilo de desenvolvimento padrão.
Se você desenvolver software complexo usando um processo de desenvolvimento interativo ou se o seu software tiver uma parte simbólica substancial (metaprogramação, outros paradigmas, álgebra computacional, composição musical, planejamento, ...), o Lisp pode ser o seu caso.
Se você trabalha no ambiente Windows (não), o C # tem uma vantagem, pois é a principal linguagem de desenvolvimento da Microsoft. A Microsoft não suporta nenhum formulário Lisp.
Você escreve:
O Lisp comum não anexa namespaces às classes. Os espaços para nome são fornecidos pelos pacotes de símbolos e são independentes das classes. Você precisa reorientar sua abordagem à programação orientada a objetos se você usar o CLOS.
Use o compilador Lisp. Informa sobre variáveis desconhecidas, pode ter recomendações de estilo (dependendo do compilador usado), fornece dicas de eficiência, ... O compilador está pressionado uma tecla.
Os Lisps comerciais como LispWorks e Allegro CL oferecem coisas semelhantes no Windows.
Os Lisps comerciais como LispWorks e Allegro CL oferecem tudo isso no Windows.
fonte
Rainier Joswig disse o melhor.
Para mim, o poder do Lisp não pode ser entendido apenas olhando uma lista de recursos do Lisp. Para Lisp, e Common Lisp em particular, o todo é maior que a soma das partes. Não são apenas as expressões REPL mais s, macros, despacho de vários métodos, fechamentos, pacotes, CLOS e reinicializações, X, Y e Z. É tudo o que há de engenhosamente integrado. É a experiência do dia a dia que é muito, muito diferente da experiência do dia a dia de outras linguagens de programação.
O Lisp parece diferente, é usado de maneira diferente, aborda-se a programação de uma maneira diferente ao usá-lo. É elegante, completo, grande e sem costura. Não é sem suas próprias manchas e pecadilhos. Certamente, há comparações com outros idiomas que podem ser feitas onde talvez não saiam à frente. Mas de maneira alguma o Lisp é apenas o idioma X mais REPL e macros, ou o idioma Y mais o despacho de vários métodos e reinicia.
fonte
Normalmente, uma macro deve ser usada para algo que não é expressável como uma chamada de função. Não sei se existe um condicional do tipo switch em C # (semelhante ao que existe em C, como switch (form) {case ...}), mas vamos assumir que existe e tem quase as mesmas restrições ( exigindo um tipo integral).
Agora, vamos supor que você gostaria de algo parecido com isso, mas despacha em strings. No lisp (bem, especificamente no Common Lisp e provavelmente em outros), isso é "apenas" uma macro de distância e, se você restringir o usuário a ter strings constantes (provavelmente não árduas) para comparar, poderá calcular (em tempo de compilação) uma sequência mínima de testes para determinar qual caso corresponde (ou usar uma tabela de hash, armazenar fechamentos, talvez).
Embora seja possível expressar isso como uma função (sequência de comparação, uma lista de casos e fechamentos a serem executados), provavelmente não pareceria "código normal" e é aí que as macros lisp têm uma vitória definitiva. Você provavelmente já usou algumas macros ou formas especiais taht são macro-like, como
defun
,defmacro
,setf
,cond
,loop
e muitos mais.fonte
Este é o melhor artigo explicativo que descobri que leva o leitor da superfície da defesa do Lisp para mostrar algumas das implicações mais profundas dos conceitos que estão sendo usados:
http://www.defmacro.org/ramblings/lisp.html
Lembro-me de ter lido em outro lugar uma idéia como esta: Outras línguas adotaram vários recursos do Lisp; coleta de lixo, digitação dinâmica, funções anônimas, fechamentos para produzir um melhor C ++ / C / Algol. No entanto, para obter todo o poder do Lisp, você precisa de todos os seus recursos essenciais; nesse momento, você tem outro dialeto do Lisp, uma família distinta de idiomas que existe de uma forma ou de outra há mais de 50 anos.
fonte