На этом шаге мы рассмотрим понятие корекурсии и ее использование при конструировании списков.
Понятие "корекурсия" определяется с помощью понятия "коданные".
В языке Haskell интерпретацией коданных являются рекурсивные абстрактные типы данных, например, списки.
Отметим, что в языке Haskell идиома коданных отсутствует; другими словами, это понятие в языке не определено в явном виде.
Поэтому мы будем моделировать коданные с помощью потенциально бесконечных списков, которые ещё называются потоками или ленивыми списками (от англ. lazy lists).
Механизм корекурсии используется с механизмом ленивых вычислений для генерации потенциально бесконечных структур данных на базе потенциально бесконечных списков (потоков).
-- Функция конструирует потенциально бесконечный -- список натуральных чисел вида [1,1,1,...] -------------------------------------------- ones:: [Integer] ones = 1 : ones -- *********************************************** -- Функция конструирует потенциально бесконечный -- список натуральных чисел вида [n+1,n+2,n+3,...] -------------------------------------------------- subNats:: Integer -> [Integer] subNats n = n : subNats (n+1)
Выделим паттерны корекурсии, основанные на использовании функционалов map, iterate, zipWith.
-- Функция конструирует потенциально бесконечный -- список натуральных чисел вида [1,1,1,...] -------------------------------------------- ones':: [Integer] ones' = 1 : map id ones' -- *********************************************** -- Функция конструирует потенциально бесконечный -- список натуральных чисел [0,1,2,...] --------------------------------------- nats:: [Integer] nats = 0 : map (+ 1) nats -- *********************************************** -- Функция конструирует потенциально бесконечный -- список нечётных натуральных чисел [1,3,5,...] ------------------------------------------------ odds:: [Integer] odds = 1 : map (+ 2) odds -- *********************************************** -- Функция конструирует потенциально бесконечный -- список чётных натуральных чисел [1,3,5,...] с -- помощью комбинатора неподвижной точки (y) -------------------------------------------- evens:: [Integer] evens = y (\f -> 0 : map (+ 2) f) where y f = f (y f)
-- Демонстрация моделирования потенциально беско- -- нечных списков с помощью функционала iterate -- -- itarate f x = x : iterate f (f x) -- -- *********************** theOnes = iterate id 1 theNats = iterate (+ 1) 0 theOdds = iterate (+ 2) 1 theEvens = iterate (+ 2) 0
-- Функция конструирует потенциально бесконечный -- список натуральных чисел [0,1,2,...] с помощью -- функционала zipWith -- ************************************* theNats1 = 0 : zipWith (+) ones theNats1
Приведем несколько демонстрационных примеров.
-- Демонстрация конструирования простейших потен- -- циально бесконечных списков (потоков, кодан- -- ных) с помощью корекурсии -- ********************************************* -- Функция конструирует потенциально бесконечный -- список натуральных чисел вида [1,1,1,...] -------------------------------------------- ones:: [Integer] ones = 1 : ones -- ********************************************* -- Функция конструирует потенциально бесконечный -- список натуральных чисел вида [1,1,1,...] -------------------------------------------- ones':: [Integer] ones' = 1 : map id ones' -- *********************************************** -- Функция конструирует потенциально бесконечный -- список натуральных чисел [0,1,2,...] --------------------------------------- nats:: [Integer] nats = 0 : map (+ 1) nats -- *********************************************** -- Функция конструирует потенциально бесконечный -- список нечётных натуральных чисел [1,3,5,...] ------------------------------------------------ odds:: [Integer] odds = 1 : map (+ 2) odds -- *********************************************** -- Функция конструирует потенциально бесконечный -- список чётных натуральных чисел [1,3,5,...] с -- помощью комбинатора неподвижной точки (y) -------------------------------------------- evens:: [Integer] evens = y (\f -> 0 : map (+ 2) f) where y f = f (y f) -- *********************************************** -- Функция конструирует потенциально бесконечный -- список натуральных чисел вида [n+1,n+2,n+3,...] -------------------------------------------------- subNats:: Integer -> [Integer] subNats n = n : subNats (n+1) -- ********************************************* -- Функция конструирует потенциально бесконечный -- список вида [n^2,(n+1)^2,(n+2)^2,...] ---------------------------------------- squares:: Integer -> [Integer] squares n = map (^2) (subNats n) -- ********************************************** -- Демонстрация моделирования потенциально беско- -- нечных списков с помощью функционала iterate -- -- itarate f x = x : iterate f (f x) -- -- *********************** theOnes = iterate id 1 theNats = iterate (+ 1) 0 theOdds = iterate (+ 2) 1 theEvens = iterate (+ 2) 0 -- ********************************************** -- Функция конструирует потенциально бесконечный -- список натуральных чисел [0,1,2,...] с помощью -- функционала zipWith -- ************************************* theNats1 = 0 : zipWith (+) ones theNats1 -- ************************************* -- Неудачные тестовые примеры: ------------------------------------------- test1 = take 5 ones == [1,1,1,1,1] test2 = take 5 nats == [0,1,2,3,4] test3 = take 5 odds == [1,3,5,7,9] test4 = take 5 evens == [0,2,4,6,8] test5 = take 5 (subNats 5) == [5,6,7,8,9] test6 = take 5 (squares 11) == [121,144,169,196,225] ----------------------------------------------------- test7 = take 5 theOnes test8 = take 5 theNats test9 = take 5 theOdds test10 = take 5 theEvens ------------------------ test11 = take 5 theNats1
-- Демонстрация конструирования потенциально бесконечных -- списков (потоков, коданных) с помощью корекурсии -- **************************************************** -- Функция конструирует потенциально бесконечный список -- чисел Фибоначчи вида [0,1,1,2,3,5,8,...] -- (первый способ) ------------------ fibLst1 = abc 0 1 where abc x0 x1 = x0 : abc x1 (x0+x1) ------------------------------------------------------- -- Функция конструирует потенциально бесконечный список -- чисел Фибоначчи вида [0,1,1,2,3,5,8,...] -- (второй способ) ---------------------------------------------------- fibLst2 = 0 : 1 : zipWith (+) fibLst2 (tail fibLst2) -- **************************************************** -- Функция конструирует потенциально бесконечный список -- чисел вида [-1,1,-1,1,-1,1,1-,...] ------------------------------------- abc = pr fibLst1 where pr (x1:x2:x3:lst) = x1*x3-x2*x2 : pr (x2:x3:lst) -- ****************************************************** -- Функция возвращает список простых чисел из [2..] -- (первый способ) ------------------------------ sieve:: [Integer] -> [Integer] sieve (0:xs) = sieve xs sieve (n:xs) = n : sieve (mark xs 1 n) where mark (y:ys) k m | k==m = 0 : mark ys 1 m | True = y : mark ys (k+1) m -- *************************************************** -- Функция возвращает список простых чисел из [2..] -- (второй способ) ---------------------- primes' = sieve' [2..] where sieve' (n:xs) = n : sieve' (filter (\m -> rem m n/=0) xs) -- *************************************************** -- Демонстрация вывода потока строк, которые конструи- -- руются по следующим правилам: -- -- (1) s ="0"; (2) s =s ++inv s , -- 1 k+1 k k -- -- где функция inv инвертирует все "биты" аргумента. -- -- Автор: Швецкий М.В. (08.12.2013, 20:25-20:32) -- *********************************************** morse = iterate inv "0" where inv str = str ++ map (\x -> if x=='1' then '0' else '1') str -- ************************************************************ -- Функция возвращает n-й элемент заданного списка -- (n=0,1,2,...) ----------------------- nth n (x:xs) | n==0 = x | True = nth (n-1) xs -- ******************************* -- Неудачные тестовые примеры: ----------------------------------- test1 = take 20 $ zip fibLst1 [0..] test2 = take 20 $ zip fibLst2 [0..] test3 = (nth 100 fibLst1,nth 100 fibLst2) test4 = (==) (take 2000 $ abc) (take 2000 $ cycle [-1,1]) test5 = take 100 (sieve [2..]) test6 = take 100 primes' test7 = take 10 morse
Корекурсию удобно использовать для генерации потоков, содержащих решения линейных и нелинейных рекуррентных соотношений.
Еще один демонстрационный пример.
-- Демонстрация конструирования потенциально бесконечных -- списков по рекуррентному соотношению второго порядка -- **************************************************** -- Функция возвращает поток по рекуррентному соотноше- -- нию второго порядка -- -- f(x )=f0, f(x )=f1, f(x )=2*x -x -- 0 1 n n-1 n-2 -- ------------------------------- f1 x0 x1 = x0 : f1 x1 (2*x1-x0) -- *************************************************** -- Функция возвращает поток по рекуррентному соотноше- -- нию второго порядка -- -- f(x )=f0, f(x )=f1, f(x )=x ^2 -x ^2 -- 0 1 n n-1 n-2 -- --------------------------------- f2 x0 x1 = x0 : f2 x1 (x1^2-x0^2) -- ********************************** -- Два варианта реализации вычислений ------------------------------------------- f3 x0 x1 k = x0 : f3 x1 (x1^2-x0+k^2) (k+1) ------------------------------------------- f31 n | n==0 = 1 | n==1 = 2 | True = (f31 (n-1))^2-f31 (n-2)+n^2 -- *************************************** -- Неудачные тестовые примеры: ------------------------------ test1 = take 12 (f1 1 3) test2 = take 12 (f2 0 1) test3 = take 12 (f3 1 2 2) == take 12 (map f31 [0..])
Разумеется, что можно построить корекурсивные функционалы, например, функционал comap:
comap f (x:xs) = f x : comap f xs
------------------------------------------
test1 = take 10 (comap (\x -> x^2) [1..5])
test2 = take 10 (comap (\x -> x^2) [1..])
test3 = take 10 (map (\x -> x^2) [1..])
На следующем шаге мы рассмотрим конструирование потенциально бесконечных списков с помощью двух точек "..".