Мы можем обратиться к Даану Лейджену HMF за несколькими идеями. (Он имеет дело со связующими для «форалов», которые также кажутся коммутативными.
В частности, он связывает переменные в порядке появления в теле.
Тогда сравнение терминов включает в себя сколемизацию и сравнение результатов.
Мы можем добиться большего успеха, заменив этот проход сколемизации на локально безымянное представление .
data Bound t a = Bound {-# UNPACK #-} !Int t | Unbound a
instance Functor (Bound t) where ...
instance Bifunctor Bound where ...
data Expr a
= Lambdas {-# UNPACK #-} !Int (Expr (Bound () a))
| Var a
Так что теперь вхождения Bound под лямбдой - это переменные, связанные непосредственно лямбда, вместе с любой информацией о типе, которую вы хотите поместить в событие, здесь я только что использовал ().
Теперь закрытые термины полиморфны в «а» и, если вы сортируете элементы лямбды по их сайту использования (и можете гарантировать, что вы всегда канонизируете лямбду, удаляя неиспользуемые термины), альфа-эквивалентные термины сравниваются просто с (== ), и если вам нужны открытые термины, вы можете работать с Expr String или другим представлением.
Более анальная сохраняющая версия подписи для Expr и Bound будет использовать экзистенциальный тип и естественный уровень типа для определения количества связанных переменных и использовать Fin в конструкторе Bound, но так как вы уже должны поддерживать инвариант, что вы связываете не больше переменных, чем #, встречающееся в лямбда-выражении, и что информация о типах совпадает по всем Var (Bound n _)
с одним и тем же n
, это не слишком большая нагрузка для поддержки другой.
Обновление: вы можете использовать мой пакет bound
, чтобы сделать улучшенную версию полностью автономным способом!