Gen
- это, по сути, композиция монады вероятностей и древовидной монады, и наблюдаемое вами поведение в основном возникает из древовидной монады и определения Gen.filter
.
По сути, Gen.filter p g
- это простой монадический цикл, try 0
где:
-- simplified body of filter
try k =
if k > 100 then
discard -- empty tree
else do
x <- g
if p x then
pure x -- singleton tree
else
try (k + 1) -- keep looping
Таким образом, чтобы понять дерево, которое вы получили, вы должны понимать монаду дерева под нотацией do
.
Монада дерева
Тип Tree
для ежа , который внутренне используется Gen
, выглядит примерно так (если вы смотрите на связанную реализацию в еже, установитеm ~ Maybe
):
data Tree a = Empty | Node a [Tree a] -- node label and children
Существует много других Tree
-подобных типов, которые являются монадами, и монадное связывание (>>=)
обычно принимает форму подстановки деревьев.
Допустим, у вас есть дерево t = Node x [t1, t2, ...] :: Tree a
и продолжение / замена k :: a -> Tree b
, которое заменяет каждый узел / переменную x :: a
на дерево k x :: Tree b
.Мы можем описать t >>= k
в два этапа, fmap
, затем join
, следующим образом.Сначала fmap
применяет подстановку к каждой метке узла.Таким образом, мы получаем дерево, где каждый узел помечен другим деревом.Для конкретности, скажем k x = Node y [u1, u2, ...]
:
fmap k t
=
Node
(k x) -- node label
[fmap k t1, fmap k t2, ...] -- node children
=
Node
(Node y [u1, u2, ...]) -- node label
[fmap k t1, fmap k t2, ...] -- node children
Затем шаг join
сглаживает структуру вложенного дерева, объединяя дочерние элементы изнутри метки с внешними:
t >>= k
=
join (fmap k t)
=
Node
y
([join (fmap k t1), join (fmap k t2), ...] ++ [u1, u2, ...])
завершите экземпляр Monad
, обратите внимание, что у нас есть pure x = Node x []
.
Цикл try
Теперь, когда у нас есть некоторая интуиция для монады дерева, мы можем обратиться к вашему конкретному генератору.Мы хотим оценить try k
выше, где p = (== 'x')
и g = elements "yx"
.Я машу здесь руками, но вы должны представить, что g
оценивает случайным образом либо дерево Node 'y' []
(генерирует 'y'
без усадок), иначе.pure 'y'
или Node 'x' [Node 'y' []]
(генерировать 'x'
и сжиматься до 'y'
; действительно, "elements
сжимается влево"), и что каждое вхождение g
не зависит от других, поэтому мы получаемдругой результат при повторной попытке.
Давайте рассмотрим каждый случай отдельно.Что будет, если g = pure 'y'
?Предположим, k <= 100
, что мы находимся в else
ветви верхнего уровня if
, уже упрощенной ниже:
-- simplified body of filter
try k = do
c <- pure 'y' -- g = pure 'y'
if c == 'x' then -- p c = (c == 'x')
pure c
else
try (k + 1)
-- since (do c <- pure 'y' ; s c) = s 'y' (monad law) and ('y' == 'x') = False
try k = try (k + 1)
Так что все времена, когда g
оценивается как pure 'y'
, заканчиваютсяупростится как рекурсивный термин try (k + 1)
, и мы останемся со случаями, когда g
оценивает другое дерево Node 'x' [Node 'y' []]
:
try k = do
c <- Node 'x' [Node 'y' []] -- g
if c == 'x' then
pure c
else
try (k + 1)
Как показано в предыдущем разделе, монадное связываниеэквивалентно следующему, и мы заканчиваем с некоторыми эквациональными рассуждениями.
try k = join (Node (s 'x') [Node (s 'y') []])
where
s c = if c == 'x' then pure c else try (k + 1)
try k = join (Node (pure 'x') [Node (try (k + 1)) []])
try k = join (Node (pure 'x') [pure (try (k + 1))] -- simplifying join
try k = Node 'x' [join (pure (try (k + 1)))] -- join . pure = id
try k = Node 'x' [try (k + 1)]
В итоге, начиная с try 0
, с половиной вероятности try k = try (k + 1)
и с другой половиной try k = Node 'x' [try (k + 1)]
, наконец, мы остановимсяна try 100
.Это объясняет дерево, которое вы наблюдаете.
try 0 = Node 'x' [Node 'x' [ ... ]] -- about 50 nodes
(Я полагаю, что это также дает хотя бы частичный ответ на ваш другой вопрос , поскольку это показывает, насколько сокращается Gen.filter
часто сводится к повторному запуску генератора с нуля.)