Эффективная реализация катаморфизма в Scala
Для типа данных, представляющего натуральные числа:
sealed trait Nat
case object Z extends Nat
case class S(pred: Nat) extends Nat
В Scala приведен простейший способ реализации соответствующего катаморфизма:
def cata[A](z: A)(l: Nat)(f: A => A): A = l match {
case Z => z
case S(xs) => f( cata(z)(xs)(f) )
}
Однако, поскольку рекурсивный вызов cata не находится в хвостовой позиции, это может легко вызвать переполнение стека.
Какие альтернативные варианты реализации позволят избежать этого? Я бы предпочел не идти по пути F-алгебр, если интерфейс, представленный в конечном итоге кодом, не будет выглядеть так же просто, как указано выше.
РЕДАКТИРОВАТЬ: Похоже, что это может иметь непосредственное отношение: можно ли использовать продолжения, чтобы сделать хвост foldRight рекурсивным?
2 ответа
Если бы вы применяли катаморфизм в списках, это было бы то, что в Хаскеле мы называем foldr
, Мы знаем это foldr
не имеет хвосто-рекурсивного определения, но foldl
делает. Так что, если вы настаиваете на программе с рекурсивной версией, правильнее всего сделать обратный аргумент списка (хвостовой рекурсивно, в линейном времени), а затем использовать foldl
вместо foldr
,
В вашем примере используется более простой тип данных натуральных данных (и действительно "эффективная" реализация будет использовать машинные целые числа, но мы согласимся оставить это в стороне). Что является обратной стороной одного из ваших натуральных чисел? Только само число, потому что мы можем думать о нем как о списке без данных в каждом узле, поэтому мы не можем сказать разницу, когда оно перевернуто! И что эквивалентно foldl
? Это программа (простите за псевдокод)
def cata(z, a, f) = {
var x = a, y = z;
while (x != Z) {
y = f(y);
x = pred(x)
}
return y
}
Или как хвостовая рекурсия Scala,
def cata[A](z: A)(a: Nat)(f: A => A): A = a match {
case Z => z
case S(b) => cata( f(z) )(b)(f)
}
Будет ли это делать?
Да, именно этот мотивирующий пример приведен в статье " Клоуны" слева от меня, шутники справа ("Рассеивание структур данных") (обновленная, лучшая, но несвободная версия здесь http://dl.acm.org/citation.cfm?id=1328474).
Основная идея заключается в том, что вы хотите превратить вашу рекурсивную функцию в цикл, поэтому вам нужно выяснить структуру данных, которая отслеживает состояние процедуры, которая
- Что вы уже рассчитали
- Что тебе осталось сделать.
Тип этого состояния зависит от структуры типа, над которым вы делаете сгиб, в любой точке сгиба вы находитесь в каком-то узле дерева, и вам необходимо запомнить древовидную структуру "остальной части дерева"., В статье показано, как вы можете вычислить этот тип состояния механически. Если вы сделаете это для списков, вы получите состояние, которое вам нужно отслеживать, это
- Операция запускается на всех предыдущих значениях.
- Список элементов, оставленных для обработки.
Что именно foldl
отслеживает, так что это своего рода совпадение foldl
а также foldr
может быть дано того же типа.