Haskell, Lisp e verbosity [fechado]

104

Para aqueles de vocês experientes em Haskell e algum sabor de Lisp, estou curioso para saber como é "agradável" (para usar um termo horrível) escrever código em Haskell versus Lisp.

Algumas informações básicas: estou aprendendo Haskell agora, tendo trabalhado anteriormente com Scheme e CL (e uma pequena incursão em Clojure). Tradicionalmente, você poderia me considerar um fã de linguagens dinâmicas pela sucinta e rapidez que elas fornecem. Eu rapidamente me apaixonei pelas macros Lisp, pois me deram mais uma maneira de evitar verbosidade e clichê.

Estou achando Haskell incrivelmente interessante, pois ele está me apresentando a formas de codificação que eu não sabia que existiam. Definitivamente tem alguns aspectos que parecem ajudar a alcançar agilidade, como facilidade de escrever funções parciais. No entanto, estou um pouco preocupado em perder macros Lisp (presumo que as perdi; verdade seja dita, talvez ainda não tenha aprendido sobre elas?) E com o sistema de tipagem estática.

Alguém que fez uma boa codificação na mente de ambos os mundos comentaria sobre como as experiências diferem, o que você prefere e se essa preferência é situacional?

J Cooper
fonte

Respostas:

69

Resposta curta:

  • quase tudo que você pode fazer com macros, você pode fazer com uma função de ordem superior (e eu incluo mônadas, setas, etc.), mas pode exigir mais reflexão (mas apenas na primeira vez, e é divertido e você será um melhor programador para isso), e
  • o sistema estático é suficientemente geral para nunca atrapalhar o seu caminho e, de forma surpreendente, na verdade "ajuda a obter agilidade" (como você disse) porque quando seu programa compila, você pode ter quase certeza de que está correto, então essa certeza permite que você tente coisas que você poderia ter medo de tentar - há uma sensação "dinâmica" na programação, embora não seja a mesma que com Lisp.

[Nota: existe um " Template Haskell " que permite escrever macros como no Lisp, mas estritamente falando, você nunca deve precisar dele.]

ShreevatsaR
fonte
15
De Conor McBride, citado por Don Stewart: 'Gosto de pensar nos tipos que distorcem nossa gravidade, de modo que a direção que precisamos viajar [para escrever programas corretos] se torna' ladeira abaixo '.' O sistema de tipos torna surpreendentemente fácil escrever programas corretos ... veja este post e seus novos compartilhamentos.
ShreevatsaR
3
As funções de alta ordem não podem substituir as macros e, de fato, o CL tem as duas por algum motivo. O verdadeiro poder das macros em CL é que elas permitem ao desenvolvedor introduzir novos recursos de linguagem que ajudam a expressar melhor a solução para um problema, sem ter que esperar por uma nova versão da linguagem como em Haskell ou Java. Por exemplo, se Haskell tivesse esse poder, não haveria necessidade dos autores de Haskell escreverem extensões GHC, pois elas poderiam ser implementadas pelos próprios desenvolvedores como macros a qualquer momento.
mljrg
@mljrg Você tem um exemplo concreto? Veja os comentários sobre a resposta de Hibou57 abaixo, onde um suposto exemplo acabou sendo duvidoso. Eu estaria interessado em saber o que você quer dizer (por exemplo, código Haskell com e sem macros).
ShreevatsaR
4
Retire o currying de Haskell. Você poderia implementá-lo com o que sobraria em Haskell? Outro exemplo: suponha que Haskell não suporte correspondência de padrões, você poderia adicioná-lo sem a necessidade de os desenvolvedores do GHC suportarem? No CL, você pode usar macros para estender a linguagem à vontade. Suponho que seja por isso que a linguagem CL não mudou desde seu padrão nos anos 90, enquanto Haskell parece ter um fluxo interminável de extensões no GHC.
mljrg
64

Em primeiro lugar, não se preocupe em perder recursos específicos, como a digitação dinâmica. Como você está familiarizado com Common Lisp, uma linguagem notavelmente bem projetada, suponho que você saiba que uma linguagem não pode ser reduzida a seu conjunto de recursos. Trata-se de um todo coerente, não é?

Nesse sentido, Haskell brilha tanto quanto o Common Lisp. Seus recursos se combinam para fornecer a você uma forma de programação que torna o código extremamente curto e elegante. A falta de macros é atenuada um pouco por conceitos mais elaborados (mas, da mesma forma, mais difíceis de entender e usar) como mônadas e setas. O sistema de tipos estáticos aumenta seu poder, em vez de atrapalhar, como acontece na maioria das linguagens orientadas a objetos.

Por outro lado, a programação em Haskell é muito menos interativa do que Lisp, e a tremenda quantidade de reflexão presente em linguagens como Lisp simplesmente não se encaixa na visão estática do mundo que Haskell pressupõe. Os conjuntos de ferramentas disponíveis para você são, portanto, bastante diferentes entre os dois idiomas, mas difíceis de comparar.

Pessoalmente, prefiro a forma de programação Lisp em geral, pois sinto que se adapta melhor à maneira como trabalho melhor. No entanto, isso não significa que você também deve fazer isso.

Matthias Benkard
fonte
4
Você poderia falar um pouco mais sobre "a programação em Haskell é muito menos interativa". GHCi não fornece realmente tudo que você precisa?
Johannes Gerer
3
@JohannesGerer: Eu não tentei, mas até onde li, GHCi não é um shell na imagem em execução, onde você pode redefinir e estender partes arbitrárias de todo o programa enquanto ele está sendo executado. Além disso, a sintaxe Haskell torna muito mais difícil copiar fragmentos de programa entre o repl e o editor programaticamente.
Svante
13

Há menos necessidade de metaprogramação em Haskell do que em Common Lisp porque muito pode ser estruturado em torno de mônadas e a sintaxe adicionada faz com que as DSLs incorporadas pareçam menos como uma árvore, mas sempre há o Template Haskell, como mencionado por ShreevatsaR , e até mesmo Liskell (semântica Haskell + Lisp sintaxe) se você gostar dos parênteses.

Herrmann
fonte
1
o link Liskell está morto, mas hoje em dia existe Hackett .
Will Ness de
10

Em relação às macros, aqui está uma página que fala sobre isso: Hello Haskell, Goodbye Lisp . Ele explica um ponto de vista em que as macros simplesmente não são necessárias em Haskell. Ele vem com um pequeno exemplo para comparação.

Exemplo de caso em que uma macro LISP é necessária para evitar a avaliação de ambos os argumentos:

(defmacro doif (x y) `(if ,x ,y))

Caso de exemplo em que Haskell não avalia sistematicamente ambos os argumentos, sem a necessidade de algo como uma definição de macro:

doif x y = if x then (Just y) else Nothing

E voilà

Hibou57
fonte
17
Esse é um equívoco comum. Sim, em Haskell, preguiça significa que você não precisa de macros quando deseja evitar a avaliação de algumas partes de uma expressão, mas esses são apenas o subconjunto mais trivial de todos os usos de macro. Google para "The Swine Before Perl" para uma palestra demonstrando uma macro que não pode ser feita com preguiça. Além disso, se você não quer um pouco de pouco para ser rigoroso, então você não pode fazer isso como uma função - espelhando o fato de que o esquema de delaynão pode ser uma função.
Eli Barzilay
8
@Eli Barzilay: Não acho esse exemplo muito convincente. Aqui está uma tradução simples e completa em Haskell do slide 40: pastebin.com/8rFYwTrE
Reid Barton
5
@Eli Barzilay: Não entendo sua resposta. accept é o (E) DSL. A acceptfunção é análoga à macro delineada nas páginas anteriores, e a definição de vé exatamente paralela à definição de vno Esquema no slide 40. As funções Haskell e Esquema computam a mesma coisa com a mesma estratégia de avaliação. Na melhor das hipóteses, a macro permite que você exponha mais da estrutura do seu programa para o otimizador. Você dificilmente pode reivindicar isso como um exemplo em que as macros aumentam o poder expressivo da linguagem de uma forma não replicada por avaliação preguiçosa.
Reid Barton
5
@Eli Barzilay: Em um esquema preguiçoso hipotético, você poderia escrever isto: pastebin.com/TN3F8VVE Minha afirmação geral é que esta macro compra muito pouco: sintaxe ligeiramente diferente e um tempo mais fácil para o otimizador (mas não importa para um "compilador suficientemente inteligente"). Em troca, você se prendeu em uma linguagem inexpressiva; como você define um autômato que corresponde a qualquer letra sem listar todas elas? Além disso, não sei o que você quer dizer com "usá-lo em todas as sublistas" ou "o uso obrigatório de onde com seu próprio escopo".
Reid Barton,
15
Ok eu desisto. Aparentemente, sua definição de DSL é "os argumentos para uma macro" e, portanto, meu exemplo de Scheme preguiçoso não é uma DSL, apesar de ser sintaticamente isomórfico ao original ( automatontornando- se , tornando- se letrec, :tornando- se nada nesta versão). Tanto faz. accept->
Reid Barton,
9

Sou um programador Common Lisp.

Depois de tentar Haskell há algum tempo, meu resultado pessoal era continuar com CL.

Razões:

  • tipagem dinâmica (confira Dynamic vs. Static Typing - A Pattern-Based Analysis de Pascal Costanza )
  • argumentos opcionais e de palavra-chave
  • sintaxe de lista homoicônica uniforme com macros
  • sintaxe de prefixo (não há necessidade de lembrar as regras de precedência)
  • impuro e, portanto, mais adequado para prototipagem rápida
  • sistema de objeto poderoso com protocolo de meta-objeto
  • padrão maduro
  • ampla gama de compiladores

Haskell tem seus próprios méritos, é claro, e faz algumas coisas de uma maneira fundamentalmente diferente, mas simplesmente não funciona a longo prazo para mim.

Leslie P. Polzer
fonte
Ei, por acaso você tem o título daquele jornal da Costanza ao qual fez um link? Parece que esse arquivo foi movido.
Michiakig
2
Observe que haskell também suporta a sintaxe de prefixo, mas eu diria que monad >> = seria muito feio usá-lo. Também discordo com a impureza sendo uma bênção: P
alternativa
2
Gosto desta observação: ainda não reunimos dados empíricos sobre se esse problema causa problemas sérios em programas do mundo real.
Jerome Baum,
22
Nenhum dos exemplos nesse artigo (Pascal Costanza, Dynamic vs. Static Typing - A Pattern-Based Analysis ) se aplica a Haskell. Eles são todos específicos de Java (ou mais precisamente, específicos para "programação orientada a objetos" ) e não consigo ver nenhum desses problemas surgindo em Haskell. Da mesma forma, todos os seus outros argumentos são discutíveis: pode-se também dizer que Haskell é "puro e, portanto, mais adequado para prototipagem rápida", que a sintaxe do prefixo não é obrigatória, que não tem uma ampla gama de compiladores que fazem coisas diferentes , etc.
ShreevatsaR
11
Na verdade, esse artigo é quase totalmente irrelevante para Haskell. " dilbert = dogbert.hire(dilbert);" ?? Duvido que muitos programadores de Haskell consigam ler isso sem se mexer um pouco.
esquerda por volta de
6

Em Haskell você pode definir uma função if, o que é impossível no LISP. Isso é possível devido à preguiça, que permite mais modularidade nos programas. Este artigo clássico: Por que o FP é importante, de John Hughes, explica como a preguiça aumenta a capacidade de composição.

Luntain
fonte
5
Scheme (um dos dois dialetos LISP principais) realmente tem avaliação preguiçosa, embora não seja o padrão como em Haskell.
Randy Voet,
7
(defmacro doif (xy) `(if, x, y))
Joshua Cheek
4
A macro não é o mesmo como uma função - macros não funcionam bem com as funções de ordem superior, como fold, por exemplo, enquanto que as funções não-rígidas fazer .
Tikhon Jelvis
5

Existem coisas muito legais que você pode conseguir no Lisp com macros que são complicadas (se possível) em Haskell. Tome por exemplo a macro `memoize '(veja o Capítulo 9 do PAIP de Peter Norvig). Com ele, você pode definir uma função, digamos foo, e então simplesmente avaliar (memoize 'foo), que substitui a definição global de foo por uma versão memoizada. Você pode obter o mesmo efeito em Haskell com funções de ordem superior?


fonte
3
Não exatamente (AFAIK), mas você pode fazer algo semelhante modificando a função (presumindo que seja recursiva) para fazer com que a função seja chamada recursivamente como um parâmetro (!) Em vez de simplesmente chamar a si mesma pelo nome: haskell.org/haskellwiki/Memoization
j_random_hacker
6
Você pode adicionar foo a uma estrutura de dados preguiçosa, onde o valor será armazenado uma vez calculado. Isso será efetivamente o mesmo.
Theo Belaire
Tudo em Haskell é memoized e provavelmente embutido quando necessário por padrão pelo compilador Haskell.
aoeu256 01 de
4

À medida que continuo minha jornada de aprendizado com Haskell, parece que uma coisa que ajuda a "substituir" macros é a capacidade de definir seus próprios operadores de infixo e personalizar sua precedência e associatividade. Meio complicado, mas um sistema interessante!

J Cooper
fonte