Чтобы немного расширить ответ Эдварда З. Янга:
Самый простой способ определить ваши операторы здесь, вероятно, как тип данных, наряду с типами атомарных значений в конечных узлах и дереве выражений в целом:
data Fun = Add | Mul | Sub | Div | Exp deriving (Eq, Ord, Show)
data Val a = Lit a | Var String deriving (Eq, Ord, Show)
data ExprTree a = Node String Fun (ExprTree a) (ExprTree a)
| Leaf String (Val a)
deriving (Eq, Ord, Show)
Затем вы можете определить ExprTree a
как экземпляр Num
и еще много чего:
instance (Num a) => Num (ExprTree a) where
(+) = Node "" Add
(*) = Node "" Mul
(-) = Node "" Sub
negate = Node "" Sub 0
fromInteger = Leaf "" . Lit
... что позволяет создавать естественные выражения без меток:
*Main> :t 2 + 2
2 + 2 :: (Num t) => t
*Main> 2 + 2 :: ExprTree Int
Node "" Add (Leaf "" (Lit 2)) (Leaf "" (Lit 2))
Также обратите внимание на приведенные выше пункты deriving
в определениях данных, в частности Ord
; это говорит компилятору автоматически создавать отношение упорядочения для значений этого типа. Это позволяет вам сортировать их последовательно, что означает, что вы можете, например, определить канонический порядок подвыражений, чтобы при перестановке коммутативных операций вы не застревали в цикле. Учитывая некоторые канонические сокращения и подвыражения в каноническом порядке, в большинстве случаев вы сможете использовать автоматическое соотношение равенства, заданное Eq
, для проверки эквивалентности подвыражений.
Обратите внимание, что метки будут влиять на порядок и равенство здесь. Если это нежелательно, вам нужно написать собственные определения для Eq
и Ord
, очень похожие на те, которые я дал для Num
.
После этого вы можете написать некоторые функции обхода и сокращения, выполнять такие операции, как применение операторов, выполнение подстановки переменных и т. Д.