А вот, кстати, при проверке лямбда-калькуляторов, написанных студентами, возникла такая задачка.
Пускай мы реализовали на Хаскелле лямбда-термы
data Term =
Var String
| App Term Term
| Lam String Term
проверку их альфа-эквивалентности (можно и точное совпадение, для наших целей не важно):
alphaEq :: Term -> Term -> Bool
и
(
Read more... )
reduceToNF показывает все итерации, кроме последней :))
Ведь это имеется в виду, да?
reduceToNF term = unfoldr step (term,True) where
step t,False = Nothing
step t,True = let next = reduce t in
Just(t, not $ t `alphaEq` next)
После чего сразу захотелось плюнуть на unfoldr и сделать то же самое на iterate / takeWhile...
Тем более, что она по смыслу более точно подходит: у нас список итераций.
reduceToNF reduce equal = map fst . takeWhile snd . iterate (step . fst) . (,True)
where
step t =
let n = reduce t in
(t, not $ t `alphaEq` n)
Ибо, один чёрт мы заворачиваем-выворачиваем значения, только вместо Maybe Term здесь (Term,Bool).
Reply
Тут есть другая бяка в схеме, которую, кстати, непонятно каким test suit'ом ловить. Эта схема врёт на одном достаточно просто устроенном терме.
Reply
Я предполагаю, что оно может возникнуть по 4 причинам:
1) баг в alphaEq, который некоторые эквивалентные термы не считает таковыми
2) баг в reduce, приводящий к циклу длиной более 1 либо к "как это будет редукция наоборот"
3) терм, описывающий рекурсивное выражение, который при редукции не сокращается, а раскрывается всё больше и больше
4) подлинно рекурсивный терм, вида let t = App t t in t - для которого синтаксическое дерево анализировать бесполезно, нужна редукция на графе
Reply
Reply
Может, дело в порядке подстановки? Что-то типа
const = \x . \y . x
bigboom = \x . какая-то-адская-рекурсивная-фигня
fortytwo = \x . const 42 (bigboom x)
Reply
Reply
f x y = y x f
f f f
Reply
x ~> y ~> z ~> x ~> y ~> z ~> x ~> ...
то это была бы бесконечная цепочка редукций, которую наша схема правильно бы воспроизвела.
Reply
Я так понимаю, что
f x y = y x f
f f f
будет редуцироваться так:
(upd: ~> наверное, что-то своё означают, напишу-ка, как знаю)
(x -> y -> y x f) f f
(y -> y f f) f
f f f = (x -> y -> y x f) f f
(y -> y f f) f
...
Поскольку проверялка будет сравнивать (x -> y -> y x f) f f и (y -> y f f) f, она просто никогда не остановится.
Reply
f x y = y x f
f = \x y -> y x f
f = (\z x y -> y x z) f
То что в скобках теперь неподвижная точка для f, используя, например, Y-комбинатор fixY = \p -> (\q -> p (q q)) (\q -> p (q q)) имеем:
f = fixY (\z x y -> y x z)
То есть f и, следовательно, f f f будут термами фиксированной конечной длины, поэтому проблема не в зависании проверялки.
Reply
Reply
Кстати, отсюда мораль: вместо детерминированной редукции, которая может пойти по дороге в ад, надо недетерминированную: строить дерево всех одношаговых редукций и останавливаться на любом нередуцируемом терме (поиск вширь, а не вглубь). Ну или для приличия сделать ещё несколько попыток для нахождения второго нередуцируемого терма...
монадическая
reduce :: Term -> [Term]
и пусть она возвращает только редукции, а не оригинал.
Т.е. вместо if canReduce t then doReduce t
там будет t'<-allReduces t
Возможно, прямо в недрах делать отсеивание опасностей...
Reply
Reply
Если редукция идёт по единственному пути и делает всё правильно;
если функция построения списка итераций до первого равенства смежных элементов делает всё правильно;
то остаётся уязвимость в alphaEq.
Тогда это не уязвимость, а баг.
Потому что для редукции известны несколько разных стратегий, дающих существенно разные результаты; а для итераций - проблема остановки. То есть там глюки возможны в штатном режиме.
Reply
(\x -> x x) (\x -> x x)
Этот терм не имеет нормальной формы, превращаясь сам в себя при бета-редукции единственного редекса. И при этом не меняясь ни капли.
Reply
Reply
Leave a comment