проблема
Рассмотрим следующую проблему дизайна в Haskell. У меня есть простой, символический EDSL, в котором я хочу выразить переменные и общие выражения (многомерные полиномы), такие как x^2 * y + 2*z + 1
. Кроме того, я хочу выразить некоторые символические уравнения над выражениями, скажем x^2 + 1 = 1
, а также определениями , например x := 2*y - 2
.
Цель состоит в том, чтобы:
- Имейте отдельный тип для переменных и общих выражений - некоторые функции могут применяться к переменным, а не к сложным выражениям. Например, оператор определения
:=
может иметь тип,(:=) :: Variable -> Expression -> Definition
и не должно быть возможности передавать сложное выражение в качестве параметра левой части (хотя должна быть возможность передавать переменную в качестве параметра правой части без явного приведения ). , - Имейте выражения в качестве экземпляра
Num
, чтобы можно было добавлять целочисленные литералы в выражения и использовать удобные обозначения для общих алгебраических операций, таких как сложение или умножение, без введения некоторых вспомогательных операторов-оболочек.
Другими словами, я хотел бы иметь неявное и статическое приведение типов (приведение) переменных к выражениям. Теперь я знаю, что в Хаскеле нет неявных приведений типов. Тем не менее, некоторые объектно-ориентированные концепции программирования (простое наследование, в данном случае) являются выразимы в системе типов в Haskell, с или без расширений языка. Как я могу удовлетворить оба вышеупомянутых пункта, сохраняя легкий синтаксис? Это вообще возможно?
обсуждение
Понятно, что главной проблемой здесь является Num
ограничение типа, например
(+) :: Num a => a -> a -> a
В принципе, можно написать один (обобщенный) алгебраический тип данных для переменных и выражений. Тогда можно было бы написать :=
таким образом, что левостороннее выражение распознается, и принимается только конструктор переменной, в противном случае возникает ошибка времени выполнения. Это, однако, не чистое, статичное (то есть время компиляции) решение ...
пример
В идеале я хотел бы получить легкий синтаксис, такой как
computation = do
x <- variable
t <- variable
t |:=| x^2 - 1
solve (t |==| 0)
В частности, я хочу запретить нотацию, например, t + 1 |:=| x^2 - 1
так как она
:=
должна давать определение переменной, а не целое выражение в левой части.
class FromVar e
метод сfromVar :: Variable -> e
и предоставить экземпляры дляExpression
иVariable
, затем ваши переменные имеют полиморфные типыx :: FromVar e => e
и т. д. Я не проверял, насколько хорошо это работает, так как я сейчас на моем телефоне.FromVar
класс типов будет полезен. Я хочу избежать явных приведений при сохраненииExpr
экземпляраNum
. Я отредактировал вопрос, добавив пример нотации, которую хотел бы получить.Ответы:
Чтобы использовать полиморфизм, а не подтипы (потому что это все, что у вас есть в Haskell), не думайте, что «переменная является выражением», но «у переменных и выражений есть некоторые общие операции». Эти операции могут быть помещены в класс типов:
Тогда, вместо того, чтобы кастовать вещи, сделайте вещи полиморфными. Если у вас есть
v :: forall e. HasVar e => e
, его можно использовать как в качестве выражения, так и в качестве переменной.Скелет, чтобы сделать код ниже проверки типа: https://gist.github.com/Lysxia/da30abac357deb7981412f1faf0d2103
источник
V
. Первоначально это не то, что я хотел, но, возможно, я был слишком быстр, чтобы отклонить это ... Возможно, я не могу избавиться от непрозрачногоV
. На соответствующей заметке, как я могу создать экземплярV (forall e . HasVar e => e)
? В Coq я бы использовал вычисления типов и сопоставление с образцом для индуктивного типа, но неясно, как этого добиться в Haskell.w :: Variable
как-нибудь и применитьfromVar
к немуvariable = (\w -> V (fromVar w)) <$> (_TODO_ :: Solver Variable)
.V
можно было бы избежать с помощью непредсказуемых типов, но это все еще WIP. Или мы можем сделатьvariable
продолжение с полиморфным аргументом явно, а не косвенно через(>>=)
.