Как добавить поля, которые только кэшируют что-то в ADT?

Часто яm в добавлении полей к ADT, которые запоминают только некоторую избыточную информацию. Но у меня нетЯ разобрался полностью, как сделать это красиво и эффективно.

Лучший способ показать проблему - это сделать пример. Предположим, мыработа с нетипизированными лямбда-терминами:

type VSym = String

data Lambda = Var VSym 
            | App Lambda Lambda
            | Abs VSym Lambda

Время от времени нам нужно вычислять множество свободных переменных члена:

fv :: Lambda -> Set VSym
fv (Var v)    = Set.singleton v
fv (App s t)  = (fv s) `Set.union` (fv t)
fv (Abs v t)  = v `Set.delete` (fv t)

Вскоре мы понимаем, что повторные вычисленияfv являются узким местом нашего приложения. Мы бы хотели как-то добавить его к типу данных. Подобно:

data Lambda1 = Var (Set VSym) VSym
             | App (Set VSym) Lambda Lambda
             | Abs (Set VSym) VSym Lambda

Но это делает определение довольно некрасивым. Почти(Set VSym) занимает больше места, чем все остальные. Кроме того, это нарушает сопоставление с образцом во всех функциях, которые используютLambda, И что еще хуже, если позже мы решим добавить какое-нибудь другое поле для запоминания, мыПридется переписать все шаблоны снова.

Как разработать общее решение, позволяющее легко и незаметно добавлять такие поля для запоминания? Я'Я хотел бы достичь следующих целей:

data определение должно выглядеть как можно ближе к оригиналу, чтобы онос легко читаемым и понятным.Шаблонные совпадения тоже должны оставаться простыми и читаемыми.Добавление нового поля запоминания позже не должно нарушать существующий код, в частности:не нарушать существующие шаблоны,не требовать изменений там, где была использована функция, которую мы хотим запомнить (например, код, который использовалfv в этом примере) .I '

опишу мое текущее решение: чтобы сохранитьdata определения и шаблоны соответствуют как можно более загроможденнымs определить:

data Lambda' memo = Var memo VSym 
                  | App memo (Lambda' memo) (Lambda' memo)
                  | Abs memo VSym (Lambda' memo)
type Lambda = Lambda' LambdaMemo

где данные для запоминания определяются отдельно:

data LambdaMemo = LambdaMemo { _fv :: Set VSym, _depth :: Int }

Затем простая функция, которая извлекает запомненную часть:

memo :: Lambda' memo -> memo
memo (Var c _)   = c
memo (App c _ _) = c
memo (Abs c _ _) = c

(Это можно устранить с помощью именованных полей. Но тогда мы быдолжны назвать все остальные поля также.)

Это позволяет нам выбирать конкретные детали из памятки, сохраняя одну и ту же подписьfv как прежде:

fv :: Lambda -> Set VSym
fv = _fv . memo

depth :: Lambda -> Int
depth = _depth . memo

Наконец, мы объявляем эти умные конструкторы:

var :: VSym -> Lambda
var v = Var (LambdaMemo (Set.singleton v) 0) v

app :: Lambda -> Lambda -> Lambda
app s t = App (LambdaMemo (fv s `Set.union` fv t) (max (depth t) (depth s))) s t

abs :: VSym -> Lambda -> Lambda
abs v t = Abs (LambdaMemo (v `Set.delete` fv t) (1 + depth t)) v t

Теперь мы можем эффективно писать вещи, которые сочетают сопоставление с шаблоном с чтением запомненных полей, таких как

canSubstitute :: VSym -> Lambda -> Lambda -> Bool
canSubstitute x s t
  | not (x `Set.member` (fv t))
      = True -- the variable doesn't occur in `t` at all
canSubstitute x s t@(Abs _ u t')
  | u `Set.member` (fv s)
      = False
  | otherwise
      = canSubstitute x s t'
canSubstitute x s (Var _ _)
      = True
canSubstitute x s (App _ t1 t2)
      = canSubstitute x s t1 && canSubstitute x s t2

Это, кажется, решить:

Сопоставление с образцом все еще вполне разумно.Если мы добавим новое поле для запоминания, оно выиграетне нарушать существующий код.Если мы решили запомнить функцию с подписьюLambda -> Something мы можем легко добавить его в качестве нового поля для запоминания.

Что я до сих пор неМне нравится этот дизайн:

data определение нетак плохо, но все ещеmemo везде это сильно загромождает.Нам нужны умные конструкторы для построения значений, но мы используем обычные конструкторы для сопоставления с образцом. Это не так плохо, мы просто добавляем один_, но было бы неплохо иметь одну и ту же сигнатуру для конструирования и деконструкции. Я полагаюПросмотры или жеУзор Синонимы бы решить это.Вычисление запомненных полей (свободные переменные, глубина) тесно связано с умными конструкторами. Как это'Разумно предположить, что эти запомненные функции будут всегдакатаморфизмЯ считаю, что это может быть решено в некоторой степени с помощью таких инструментов, какпакет Fixpoint.

Есть идеи как его улучшить? Или есть лучшие способы решить такую проблему?

Ответы на вопрос(1)

Ваш ответ на вопрос