Problema
Considere o seguinte problema de design no Haskell. Eu tenho um EDSL simbólico simples, no qual quero expressar variáveis e expressões gerais (polinômios multivariados), como x^2 * y + 2*z + 1
. Além disso, quero expressar certas equações simbólicas sobre expressões, por exemplo x^2 + 1 = 1
, bem como definições , como x := 2*y - 2
.
O objetivo é:
- Tenha um tipo separado para variáveis e expressões gerais - certas funções podem ser aplicadas a variáveis e não expressões complexas. Por exemplo, uma definição de operador
:=
pode ser do tipo(:=) :: Variable -> Expression -> Definition
e não deve ser possível passar uma expressão complexa como parâmetro lado esquerdo (embora ele deve ser possível passar uma variável como parâmetro do lado direito, sem conversão explícita ) . - Tenha expressões como uma instância
Num
, para que seja possível promover literais inteiros para expressões e usar uma notação conveniente para operações algébricas comuns como adição ou multiplicação sem a introdução de alguns operadores auxiliares de wrapper.
Em outras palavras, eu gostaria de ter uma conversão de tipo implícita e estática (coerção) de variáveis para expressões. Agora, eu sei que, como tal, não há projeções implícitas de tipo em Haskell. No entanto, certos conceitos de programação orientada a objetos (herança simples, neste caso) são expressáveis no sistema de tipos de Haskell, com ou sem extensões de linguagem. Como eu poderia satisfazer os dois pontos acima, mantendo uma sintaxe leve? Isso é possível?
Discussão
É claro que o principal problema aqui é Num
a restrição de tipo, por exemplo
(+) :: Num a => a -> a -> a
Em princípio, é possível escrever um único tipo de dados algébrico (generalizado) para variáveis e expressões. Então, pode-se escrever :=
de tal maneira que a expressão do lado esquerdo seja discriminada e apenas um construtor de variáveis seja aceito, com um erro em tempo de execução. No entanto, essa não é uma solução limpa e estática (ou seja, em tempo de compilação) ...
Exemplo
Idealmente, gostaria de obter uma sintaxe leve, como
computation = do
x <- variable
t <- variable
t |:=| x^2 - 1
solve (t |==| 0)
Em particular, quero proibir a notação como
t + 1 |:=| x^2 - 1
uma vez que :=
deve dar uma definição de uma variável e não uma expressão inteira do lado esquerdo.
class FromVar e
com um métodofromVar :: Variable -> e
e fornecer instâncias paraExpression
eVariable
, em seguida, fazer com que suas variáveis tenham tipos polimórficosx :: FromVar e => e
etc. Não testei como isso funciona desde que estou no telefone agora.FromVar
classe seria útil. Eu quero evitar conversões explícitas, mantendoExpr
uma instância deNum
. Editei a pergunta adicionando um exemplo de uma notação que gostaria de obter.Respostas:
Para aproveitar o polimorfismo em vez de subtipar (porque é tudo o que você tem em Haskell), não pense que "uma variável é uma expressão", mas "ambas as variáveis e expressões têm algumas operações em comum". Essas operações podem ser colocadas em uma classe de tipo:
Então, ao invés de lançar coisas, torne as coisas polimórficas. Se você tiver
v :: forall e. HasVar e => e
, ele pode ser usado como expressão e como variável.Esqueleto para criar o código abaixo, digite: https://gist.github.com/Lysxia/da30abac357deb7981412f1faf0d2103
fonte
V
. Inicialmente, não era isso que eu queria, mas talvez eu fosse rápido demais para descartá-lo ... Provavelmente não consigo me livrar do opacoV
. Em uma nota relacionada, como posso criar uma instância deV (forall e . HasVar e => e)
? No Coq, eu usaria cálculos de tipos e correspondência de padrões em um tipo indutivo, mas não está claro como conseguir isso em Haskell.w :: Variable
de alguma forma e aplicarfromVar
a ele:variable = (\w -> V (fromVar w)) <$> (_TODO_ :: Solver Variable)
.V
poderia ser evitado com tipos impredicativos, mas isso ainda é WIP. Ou podemos fazervariable
pegar a continuação com um argumento polimórfico explicitamente em vez de indiretamente via(>>=)
.