Ответ 1
Первичным источником (я думаю) для loeb
является блог Dan Piponi, The Neighborhood of Infinity. Там он объясняет всю концепцию более подробно. Я немного повторю это в качестве ответа и добавлю несколько примеров.
loeb
реализует странный тип ленивой рекурсии
loeb :: Functor a => a (a x -> x) -> a x
loeb x = fmap (\a -> a (loeb x)) x
Предположим, что у нас есть a
-алгебра, функция типа Functor a => a x -> x
. Вы можете подумать об этом как о способе вычисления значения из структуры ценностей. Например, вот несколько []
-алгебр
length :: [Int] -> Int
(!! 3) :: [a] -> a
const 3 :: Num a => [a] -> a
\l -> l !! 2 + l !! 3 :: Num a => [a] -> a
видно, что эти a
-алгебры могут использовать оба значения, сохраненные в Functor
, и структуру самого Functor
.
Другой способ думать о d :: Functor a => a x -> x
- это значение x
, которое требует некоторого контекста, целого Functorized value a x
, чтобы быть вычисленным. Возможно, это более ясно написано как (изоморфное) Functor a => Reader (a x) x
, подчеркивая, что это просто значение x
, которое задерживается, ожидая контекста (a x)
, который будет создан.
type Delay q x = q -> x
Используя эти идеи, мы можем описать loeb
следующим образом. Нам дается Functor
, содержащая некоторые значения Delay
ed
Functor f => f (Delay q x)
Естественно, если бы нам дали a q
, мы могли бы преобразовать это в форму с задержкой. На самом деле, мы можем написать такую функцию
force :: Functor f => f (Delay q x) -> q -> f x
force f q = fmap ($ q) f
Что loeb
делает, обрабатывает лишний сложный случай, когда q
на самом деле f x
, сам результат этой функции. Если вы знакомы с fix
, это именно то, как мы можем произвести этот результат.
loeb :: Functor a => a (Delay (a x) x) -> a x
loeb f = fix (force f)
Итак, чтобы сделать пример, мы просто должны построить Functor
содержащий Delay
ed значения. Одним из естественных примеров этого является использование примеров списка до
> loeb [ length :: [Int] -> Int
, const 3 :: [Int] -> Int
, const 5 :: [Int] -> Int
, (!! 2) :: [Int] -> Int
, (\l -> l !! 2 + l !! 3) :: [Int] -> Int
]
[5, 3, 5, 5, 10]
Здесь мы видим, что список заполнен значениями с задержкой ожидания в результате оценки списка. Это вычисление может происходить именно потому, что в зависимости от данных нет циклов, поэтому все это можно лениво определить. Например, const 3
и const 5
оба сразу доступны как значения. length
требует, чтобы мы знали длину списка, но ни одно из значений не содержалось, поэтому оно также начинается немедленно в нашем списке фиксированной длины. Интересными являются значения, ожидающие ожиданий других значений из нашего списка результатов, но поскольку (!! 2)
заканчивается только в зависимости от третьего значения списка результатов, которое определяется const 5
и, следовательно, может быть немедленно доступно, вычисление движется вперед. То же самое происходит с (\l -> l !! 2 + l !! 3)
.
Итак, у вас есть: loeb
завершает эту странную рекурсию с задержкой. Мы можем использовать его на любом типе Functor
. Все, что нам нужно сделать, это подумать о некоторых полезных значениях Delay
ed.
В комментарии Chris Kuklewicz отмечается, что не так много можно сделать с Maybe
как ваш функтор. Это потому, что все задержанные значения над Maybe
принимают вид
maybe (default :: a) (f :: a -> a) :: Maybe a -> a
и все интересные значения Maybe (Delay (Maybe a) a)
должны быть Just (maybe default f)
с loeb Nothing = Nothing
. Поэтому в конце дня значение default
никогда не используется - мы всегда просто имеем
loeb (Just (maybe default f)) == fix f
поэтому мы можем также написать это напрямую.