Esta resposta é uma resposta às questões levantadas por illissius, ponto por ponto:
- É feio de usar. $ (fooBar '' Asdf) simplesmente não parece bom. Superficial, claro, mas contribui.
Concordo. Sinto que $ () foi escolhido para parecer que fazia parte do idioma - usando o familiar palete de símbolos de Haskell. No entanto, é exatamente isso que você não quer nos símbolos usados para a emenda de macro. Definitivamente se misturam demais, e esse aspecto cosmético é bastante importante. Gosto da aparência de emendas, porque elas são visualmente distintas.
- É ainda mais feio escrever. As citações funcionam algumas vezes, mas na maioria das vezes você precisa fazer enxertos e encanamentos manuais em AST. A [API] [1] é grande e pesada, há sempre muitos casos com os quais você não se importa, mas ainda precisa despachar, e os casos com os quais você se importa tendem a estar presentes em várias formas semelhantes, mas não idênticas (dados x newtype, estilo de registro x construtores normais e assim por diante). É chato e repetitivo escrever e complicado o suficiente para não ser mecânico. A [proposta de reforma] [2] aborda parte disso (tornando as cotações mais amplamente aplicáveis).
Também concordo com isso, no entanto, como observam alguns dos comentários em "Novas direções para o TH", a falta de boas citações AST prontas para uso não é uma falha crítica. Neste pacote WIP, busco solucionar esses problemas no formato de biblioteca: https://github.com/mgsloan/quasi-extras . Até agora, permito a emenda em mais alguns lugares do que o habitual e posso combinar os padrões nos ASTs.
- A restrição de palco é um inferno. Não conseguir dividir as funções definidas no mesmo módulo é a parte menor: a outra conseqüência é que, se você tiver uma emenda de nível superior, tudo o que estiver depois no módulo ficará fora do escopo para qualquer coisa anterior. Outras linguagens com essa propriedade (C, C ++) a tornam viável, permitindo que você encaminhe declarações, mas Haskell não. Se você precisar de referências cíclicas entre declarações emendadas ou suas dependências e dependentes, geralmente está ferrado.
Já me deparei com a questão das definições cíclicas de TH antes ... É bastante irritante. Existe uma solução, mas é feia - envolva as coisas envolvidas na dependência cíclica em uma expressão TH que combina todas as declarações geradas. Um desses geradores de declarações poderia ser apenas um quasi-quoter que aceita o código Haskell.
- É sem princípios. O que quero dizer com isso é que, na maioria das vezes, quando você expressa uma abstração, existe algum tipo de princípio ou conceito por trás dessa abstração. Para muitas abstrações, o princípio por trás delas pode ser expresso em seus tipos. Quando você define uma classe de tipo, geralmente pode formular leis que as instâncias devem obedecer e os clientes podem assumir. Se você usar o [novo recurso genérico] do GHC [3] para abstrair a forma de uma declaração de instância sobre qualquer tipo de dados (dentro dos limites), poderá dizer "para tipos de soma, funciona assim, para tipos de produtos, funciona assim" " Mas o Template Haskell é apenas macros burras. Não é abstração no nível das idéias, mas abstração no nível das ASTs, o que é melhor, mas apenas modestamente, do que abstração no nível do texto sem formatação.
Só é sem princípios se você fizer coisas sem princípios. A única diferença é que, com o compilador implementado mecanismos para abstração, você tem mais confiança de que a abstração não está vazando. Talvez democratizar o design da linguagem pareça um pouco assustador! Os criadores de bibliotecas de TH precisam documentar bem e definir claramente o significado e os resultados das ferramentas que eles fornecem. Um bom exemplo de TH com princípios é o pacote derivado: http://hackage.haskell.org/package/derive - ele usa uma DSL de forma que o exemplo de muitas das derivações / especifique / a derivação real.
- Liga você ao GHC. Em teoria, outro compilador poderia implementá-lo, mas na prática duvido que isso aconteça. (Isso contrasta com várias extensões de sistema de tipo que, embora possam apenas ser implementadas pelo GHC no momento, eu poderia facilmente imaginar ser adotado por outros compiladores no futuro e eventualmente padronizado.)
Esse é um ponto muito bom - a API do TH é bastante grande e desajeitada. Reimplementar, parece que pode ser difícil. No entanto, existem realmente apenas algumas maneiras de reduzir o problema de representar ASTs da Haskell. Eu imagino que copiar os TH ADTs e gravar um conversor na representação AST interna o levaria a uma boa parte do caminho até lá. Isso seria equivalente ao esforço (não insignificante) de criar haskell-src-meta. Também poderia ser simplesmente reimplementado imprimindo bastante o TH AST e usando o analisador interno do compilador.
Embora eu possa estar errado, não vejo o TH como uma extensão de compilador tão complicada, do ponto de vista da implementação. Este é realmente um dos benefícios de "manter as coisas simples" e não ter a camada fundamental como um sistema de modelos teoricamente atraente e estaticamente verificável.
- A API não é estável. Quando novos recursos de idioma são adicionados ao GHC e o pacote template-haskell é atualizado para suportá-los, isso geralmente envolve alterações incompatíveis com os versões anteriores dos tipos de dados TH. Se você deseja que seu código TH seja compatível com mais de uma versão do GHC, você precisa ter muito cuidado e usar
CPP
.
Este também é um bom argumento, mas um pouco dramático. Embora tenha havido adições à API recentemente, elas não foram extensivamente indutoras de quebras. Além disso, acho que, com a citação AST superior que mencionei anteriormente, a API que realmente precisa ser usada pode ser substancialmente reduzida. Se nenhuma construção / correspondência precisar de funções distintas e, em vez disso, for expressa como literais, a maior parte da API desaparecerá. Além disso, o código que você escreve seria portado mais facilmente para representações AST para idiomas semelhantes ao Haskell.
Em resumo, acho que o TH é uma ferramenta poderosa e semi-negligenciada. Menos ódio pode levar a um ecossistema de bibliotecas mais animado, incentivando a implementação de mais protótipos de recursos de linguagem. Tem sido observado que o TH é uma ferramenta sobrecarregada, que pode deixar você / fazer / quase qualquer coisa. Anarquia! Bem, é minha opinião que esse poder pode permitir que você supere a maioria de suas limitações e construa sistemas capazes de abordagens de meta-programação bastante baseadas em princípios. Vale a pena o uso de hacks feios para simular a implementação "adequada", pois dessa forma o design da implementação "adequada" ficará gradualmente claro.
Na minha versão ideal pessoal do nirvana, grande parte da linguagem sairia do compilador, para bibliotecas dessa variedade. O fato de os recursos serem implementados como bibliotecas não influencia fortemente sua capacidade de abstrair fielmente.
Qual é a resposta típica de Haskell ao código padrão? Abstração. Quais são as nossas abstrações favoritas? Funções e tipeclasses!
As classes de tipos permitem definir um conjunto de métodos que podem ser usados em todos os tipos de funções genéricas nessa classe. Entretanto, além disso, a única maneira de as classes ajudarem a evitar clichês é oferecendo "definições padrão". Agora, aqui está um exemplo de um recurso sem princípios!
Conjuntos mínimos de ligação não podem ser declarados / verificados pelo compilador. Isso pode levar a definições inadvertidas que resultam em fundo devido à recursão mútua.
Apesar da grande conveniência e poder que isso renderia, você não pode especificar padrões de superclasse, devido a instâncias órfãs http://lukepalmer.wordpress.com/2009/01/25/a-world-without-orphans/ Isso permitiria corrigir o hierarquia numérica graciosamente!
A busca por recursos do tipo TH para padrões de métodos levou a http://www.haskell.org/haskellwiki/GHC.Generics . Embora isso seja interessante, minha única experiência em depurar códigos usando esses genéricos era quase impossível, devido ao tamanho do tipo induzido e ao ADT tão complicado quanto um AST. https://github.com/mgsloan/th-extra/commit/d7784d95d396eb3abdb409a24360beb03731c88c
Em outras palavras, isso ocorreu após os recursos fornecidos pelo TH, mas foi necessário elevar um domínio inteiro da linguagem, a linguagem da construção, para uma representação do sistema de tipos. Embora eu possa vê-lo funcionando bem para o seu problema comum, para os complexos, parece propenso a produzir uma pilha de símbolos muito mais aterrorizante do que os hackers da TH.
O TH fornece a computação em tempo de compilação em nível de valor do código de saída, enquanto os genéricos obriga a elevar a parte de correspondência / recursão de padrão do código no sistema de tipos. Embora isso restrinja o usuário de algumas maneiras bastante úteis, não acho que a complexidade valha a pena.
Penso que a rejeição do TH e da metaprogramação do tipo lisp levaram à preferência por coisas como padrões de método, em vez de declarações de instâncias mais flexíveis e macroexpansíveis. A disciplina de evitar coisas que podem levar a resultados imprevisíveis é sábia, no entanto, não devemos ignorar que o sistema de tipos capazes de Haskell permite uma metaprogramação mais confiável do que em muitos outros ambientes (verificando o código gerado).