Elenco do tipo estático implícito (coerção) em Haskell

9

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 é:

  1. 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 -> Definitione 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 ) .
  2. 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 é Numa 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 - 1uma vez que :=deve dar uma definição de uma variável e não uma expressão inteira do lado esquerdo.

Maciej Bendkowski
fonte
11
talvez você possa usar a class FromVar ecom um método fromVar :: Variable -> ee fornecer instâncias para Expressione Variable, em seguida, fazer com que suas variáveis ​​tenham tipos polimórficos x :: FromVar e => eetc. Não testei como isso funciona desde que estou no telefone agora.
Mor A.
Não tenho certeza de como a FromVarclasse seria útil. Eu quero evitar conversões explícitas, mantendo Expruma instância de Num. Editei a pergunta adicionando um exemplo de uma notação que gostaria de obter.
Maciej Bendkowski 9/03

Respostas:

8

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:

class HasVar e where fromVar :: Variable -> e

instance HasVar Variable where fromVar = id
instance HasVar Expression where ...

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.

example :: (forall e. HasVar e => e) -> Definition
example v = (v := v)  -- v can be used as both Variable and Expression

 where

  (:=) :: Variable -> Expression -> Definition

Esqueleto para criar o código abaixo, digite: https://gist.github.com/Lysxia/da30abac357deb7981412f1faf0d2103

computation :: Solver ()
computation = do
  V x <- variable
  V t <- variable
  t |:=| x^2 - 1
  solve (t |==| 0)
Li-yao Xia
fonte
Interessante, obrigado! Pensei em esconder as variáveis ​​e expressões por trás dos tipos existenciais por um breve momento, no entanto, rejeitei a ideia porque ela introduzia notação adicional 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 opaco V. Em uma nota relacionada, como posso criar uma instância de V (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.
Maciej Bendkowski 9/03
11
Você pode pegar um w :: Variablede alguma forma e aplicar fromVara ele: variable = (\w -> V (fromVar w)) <$> (_TODO_ :: Solver Variable).
Li-yao Xia
11
E Vpoderia ser evitado com tipos impredicativos, mas isso ainda é WIP. Ou podemos fazer variablepegar a continuação com um argumento polimórfico explicitamente em vez de indiretamente via (>>=).
Li-yao Xia