Схемы рекурсии с несколькими типами

Прямо сейчас у меня есть AST для выражения, которое полиморфно по типу рекурсии:

data Expr a = Const Int
            | Add a a

Это было невероятно полезно, поскольку позволило мне использовать тип для простой рекурсии (Fix Expr) и еще один, когда мне нужно добавить дополнительную информацию (Cofree Expr ann).

Проблема возникает, когда я хочу ввести другой тип в эту схему рекурсии:

data Stmt a = Compound [a]
            | Print (Expr ?)

Я не уверен, что поставить для термина Expr без введения дополнительных переменных типа и нарушения совместимости со всеми общими функциями, которые я уже написал.

Можно ли это сделать, и если да, то является ли это полезным шаблоном?


person xal    schedule 28.06.2018    source источник


Ответы (1)


Перспектива схем рекурсии состоит в том, чтобы рассматривать рекурсивные типы как фиксированные точки функторов. Типом выражений является неподвижная точка следующего функтора:

data ExprF expr = Const Int
                | Add expr expr

Смысл изменения имени переменной состоит в том, чтобы сделать явным тот факт, что оно является заполнителем для фактического типа выражений, которые в противном случае были бы определены как:

data Expr = Const Int | Add Expr Expr

В Stmt есть два рекурсивных типа, Expr и сам Stmt. Таким образом, мы помещаем две дыры/неизвестные.

data StmtF expr stmt = Compound [stmt]
                     | Print expr

Когда мы берем фиксированную точку с Fix или Cofree, мы теперь решаем систему из двух уравнений (одно для Expr, одно для Stmt), и это идет с некоторым количеством шаблонов.

Вместо прямого применения Fix или Cofree мы обобщаем, принимая эти комбинаторы с фиксированной точкой (Fix, Cofree, Free) в качестве параметров при построении выражений и операторов:

type Expr_ f = f ExprF
type Stmt_ f = f (StmtF (Expr_ f))

Теперь мы можем сказать Expr_ Fix или Stmt_ Fix для неаннотированных деревьев и Expr_ (Flip Cofree ann), Stmt_ (Flip Cofree ann). К сожалению, нам приходится платить еще одну комиссию LOC, чтобы типы совпадали, и типы становятся еще более запутанными.

newtype Flip cofree a f b = Flip (cofree f a b)

(Это также предполагает, что мы хотим использовать одни и те же Fix или Cofree везде и в одно и то же время.)


Еще одно представление, которое следует учитывать (в настоящее время называется HKD):

data Expr f = Const Int
            | Add (f Expr) (f Expr)

data Stmt f = Compount [f Stmt]
            | Print (f (Expr f))

где вы абстрагируетесь только от аннотации/без аннотации (f = Identity или (,) ann), а не от рекурсии.

person Li-yao Xia    schedule 28.06.2018
comment
Шаг обобщения с синонимами типов был решающим шагом, который я не мог придумать, но теперь все кажется настолько ясным, что оно передо мной. Спасибо! - person xal; 28.06.2018
comment
Как бы вы сделали это, если Expr также ссылается на Stmt, как если бы Expr имел Block [Stmt] Expr - person MrUnleaded; 14.08.2020
comment
В случае взаимно рекурсивных типов вам понадобится операция с фиксированной точкой для функторов с несколькими параметрами: ExprF expr stmt, StmtF expr stmt. Библиотека рекурсионных схем больше не поможет. Я думаю, что compdata — хорошая альтернатива, но она немного сложнее. В этот момент кажется проще всего свернуть свои собственные схемы рекурсии. - person Li-yao Xia; 14.08.2020