Схема рекурсии от Int -> Int?
Фолд идентичность
foldr (:) []
В более общем случае, с помощью сгибов вы можете либо разрушить структуру и получить итоговое значение, либо внедрить структуру таким образом, чтобы получилась одна и та же структура вывода.
[Int] -> [Int]
или же [Int] -> Int
или же[Int] -> ?
Мне интересно, существует ли похожая идентичность с unfoldr/l.
Я знаю как получить
Int -> [Int]
с развернуть / ана.
Я ищу какой-то путь от
Int -> Int
с рекурсивной схемой.
2 ответа
Исходя из вашего замечания о факториалах, мы можем заметить, что натуральные числа можно рассматривать как рекурсивную структуру данных:
data Nat = Zero | Succ Nat
С точки зрения механизма рекурсивных схем соответствующий базовый функтор будет:
data NatF a = ZeroF | SuccF a
deriving (Functor)
NatF
однако изоморфен Maybe
, Таким образом, рекурсивные схемы удобно Maybe
базовый функтор Natural
Тип с базы. Например, вот тип ana
специализируется на Natural
:
ana @Natural :: (a -> Maybe a) -> a -> Natural
Мы можем использовать его, чтобы написать личность для Natural
:
{-# LANGUAGE LambdaCase #-}
import Numeric.Natural
import Data.Functor.Foldable
idNatAna :: Natural -> Natural
idNatAna = ana $ \case
0 -> Nothing
x -> Just (x - 1)
Коалгебра, которую мы только что дали ana
является project
заNatural
, project
будучи функцией, которая разворачивает один слой рекурсивной структуры. В терминах словаря рекурсивных схем, ana project
разворачивается личность, и cata embed
это сгиб идентичности. (Особенно, project
для списков есть uncons
от Data.List
за исключением того, что он закодирован ListF
вместо Maybe
.)
Кстати, факториальная функция может быть выражена как параморфизм натуралов ( как указано в примечании в конце этого вопроса). Мы также можем реализовать это в терминах рекурсивных схем:
fact :: Natural -> Natural
fact = para $ \case
Nothing -> 1
Just (predec, prod) -> prod * (predec + 1)
para
делает доступным на каждом рекурсивном шаге оставшуюся часть структуры, которую нужно сложить (если бы мы складывали список, это был бы его хвост). В этом случае я назвал значение, предоставленное таким образом predec
потому что на n
рекурсивный шаг снизу вверх predec
является n - 1
,
Обратите внимание, что пользовательский гиломорфизм, вероятно, является более эффективной реализацией, если вам не безразлично это. (Я не тестировал их, хотя.)
Схема рекурсии, которая имеет дело с созданием промежуточной структуры и сносом ее так, чтобы структура не появлялась на входе или выходе, является гильоморфизмом, записанным по буквам hylo
в recursion-schemes
,
Чтобы использовать hylomorphism, вам нужно указать алгебру (то, что потребляет один шаг рекурсивной структуры) и коалгебру (то, что производит один шаг рекурсивной структуры), и вам нужно иметь тип данных для типа структуры вы используете, конечно.
Вы предложили факториал, поэтому давайте посмотрим, как написать это как hylomorphism.
Один из способов взглянуть на факториал - это произведение списка чисел с обратным отсчетом от начального n
, В этом кадре мы можем думать о продукте как о нашей алгебре, снося список по одному минусу за раз, а обратный отсчет как о нашей коалгебре, составляя список как n
уменьшается.
recursion-schemes
дает нам ListF
в качестве удобного базового функтора для списков, поэтому мы будем использовать его в качестве типа данных, создаваемых коалгеброй и потребляемых алгеброй. Его конструкторы Nil
а также Cons
которые, конечно, напоминают конструкторы для полных списков, за исключением того, что ListF
как и любая базовая структура в схеме рекурсии, использует параметр типа в месте, где списки будут использовать реальную рекурсию (это означает, что Cons :: a -> b -> ListF a b
вместо (:) :: a -> [a] -> [a]
).
Так что это определяет наши типы. Сейчас определяю fact
это довольно заполняющее упражнение:
import Prelude hiding (product)
import Data.Functor.Foldable
product :: ListF Int Int -> Int
product Nil = 1
product (Cons a b) = a * b
countDown :: Int -> ListF Int Int
countDown 0 = Nil
countDown n = Cons n (n - 1)
fact :: Int -> Int
fact = hylo product countDown