Какие мотивирующие примеры для Cofree CoMonad в Haskell?

Я играл с Cofree и не могу понять.

Например, я хочу поиграть с Cofree [] Num в ghci и не могу найти интересных примеров.

Например, если я создам тип Cofree:

let a = 1 :< [2, 3]

Я ожидал бы extract a == 1, но вместо этого я получаю эту ошибку:

No instance for (Num (Cofree [] a0)) arising from a use of ‘it’
    In the first argument of ‘print’, namely ‘it’
    In a stmt of an interactive GHCi command: print it

И типа:

extract a :: (Num a, Num (Cofree [] a)) => a

Могу ли я получить несколько простых примеров, даже тривиальных, того, как использовать Cofree, скажем, с функторами: [], или Maybe, или Either, которые демонстрируют

  • extract
  • extend
  • unwrap
  • duplicate?

Перекрестная публикация: https://www.reddit.com/r/haskell/comments/4wlw70/what_are_some_motivating_examples_for_cofree/

РЕДАКТИРОВАТЬ: Руководствуясь комментарием Дэвида Янга, вот несколько лучших примеров, которые показывают, где мои первые попытки были ошибочными, однако мне все равно нравятся некоторые примеры, которые могут помочь понять интуицию Cofree:

> let a = 1 :< []
> extract a
    1
> let b = 1 :< [(2 :< []), (3 :< [])]
> extract b
    1
> unwrap b
    [2 :< [],3 :< []]
> map extract $ unwrap b
    [2,3]

person Josh.F    schedule 07.08.2016    source источник
comment
Num — это класс типов, а не тип. Что-то вроде Cofree [] Int было бы более подходящим. Также обратите внимание, что с этим типом тип конструктора становится (:<) :: Int -> [Cofree [] Int] -> Cofree [] Int, поэтому вторым аргументом должен быть список значений cofree. Простым примером может быть let a = 1 :< []. Другим небольшим примером может быть let a = 1 :< [x], где x — это другое значение Cofree [] Int.   -  person David Young    schedule 07.08.2016
comment
@DavidYoung Круто, это помогло мне понять большую часть моего понимания, и я отредактировал свой пост. Мне все еще не хватает интуиции cofree, но теперь я вижу, что 1 :< [2, 3] заставляет 1, 2, 3 каким-то образом принуждаться к какому-то причудливому типу, который я не знаю, как интерпретировать в своем уме, и я хочу, чтобы a в Cofree f a просто Int, а не какой-то придурковатый тип. Спасибо!   -  person Josh.F    schedule 07.08.2016
comment
Я думаю, что лучший способ развить интуицию в отношении Cofree — это опробовать его на нескольких функторах. Cofree Identity — это бесконечный поток. Cofree Maybe — непустой поток. Cofree ((-›) b) — это (плохая кодировка) потенциально бесконечной машины Мура. Cofree [] — розовое дерево, эквивалентное тому, что есть в Data.Tree.   -  person Edward KMETT    schedule 07.08.2016
comment
Конечно, вы не можете грокать это. Вы еще даже не съели его.   -  person JK.    schedule 09.08.2016
comment
@EdwardKMETT Cofree Возможно, это непустой поток. Вы имеете в виду непустой список, верно?   -  person jun    schedule 24.07.2021


Ответы (1)


Давайте просто повторим определение типа данных Cofree.

data Cofree f a = a :< f (Cofree f a)

Этого как минимум достаточно, чтобы диагностировать проблему на примере. Когда ты написал

1 :< [2, 3]

вы сделали небольшую ошибку, о которой сообщается скорее более тонко, чем полезно. Здесь f = [] и a что-то числовое, потому что 1 :: a. Соответственно вам нужно

[2, 3] :: [Cofree [] a]

и, следовательно

2 :: Cofree [] a

что может быть в порядке, если Cofree [] a также и экземпляр Num. Таким образом, ваше определение получает ограничение, которое вряд ли будет удовлетворено, и действительно, когда вы используете свое значение, попытка удовлетворить ограничение терпит неудачу.

Попробуйте еще раз с

1 :< [2 :< [], 3 :< []]

и тебе должно повезти.

Теперь давайте посмотрим, что у нас есть. Начните с простого. Что такое Cofree f ()? Что, в частности, такое Cofree [] ()? Последний изоморфен фиксированной точке []: древовидные структуры, в которых каждый узел представляет собой список поддеревьев, также известных как «немаркированные розовые деревья». Например.,

() :< [  () :< [  () :< []
               ,  () :< []
               ]
      ,  () :< []
      ]

Точно так же Cofree Maybe () является более или менее фиксированной точкой Maybe: копией натуральных чисел, потому что Maybe дает нам либо ноль, либо одну позицию, в которую можно вставить поддерево.

zero :: Cofree Maybe ()
zero = () :< Nothing
succ :: Cofree Maybe () -> Cofree Maybe ()
succ n = () :< Just n

Важным тривиальным случаем является Cofree (Const y) (), который является копией y. Функтор Const y не дает нет позиций для поддеревьев.

pack :: y -> Cofree (Const y) ()
pack y = () :< Const y

Далее займемся другим параметром. Он сообщает вам, какую метку вы прикрепляете к каждому узлу. Более выразительное переименование параметров

data Cofree nodeOf label = label :< nodeOf (Cofree nodeOf label)

Когда мы маркируем пример (Const y), мы получаем пары

pair :: x -> y -> Cofree (Const y) x
pair x y = x :< Const y

Когда мы присоединяем метки к узлам наших чисел, мы получаем непустые списки

one :: x -> Cofree Maybe x
one = x :< Nothing
cons :: x -> Cofree Maybe x -> Cofree Maybe x
cons x xs = x :< Just xs

А для списков мы получаем помеченные розовые деревья.

0 :< [  1 :< [  3 :< []
             ,  4 :< []
             ]
     ,  2 :< []
     ]

Эти структуры всегда «непусты», потому что существует по крайней мере верхний узел, даже если у него нет дочерних элементов, и этот узел всегда будет иметь метку. Операция extract дает вам метку верхнего узла.

extract :: Cofree f a -> a
extract (a :< _) = a

То есть extract отбрасывает контекст верхней метки.

Теперь операция duplicate украшает каждую метку собственным контекстом.

duplicate :: Cofree f a -> Cofree f (Cofree f a)
duplicate a :< fca = (a :< fca) :< fmap duplicate fca  -- f's fmap

Мы можем получить экземпляр Functor для Cofree f, посетив все дерево.

fmap :: (a -> b) -> Cofree f a -> Cofree f b
fmap g (a :< fca) = g a :< fmap (fmap g) fca
    --                     ^^^^  ^^^^
    --                 f's fmap  ||||
    --                           (Cofree f)'s fmap, used recursively

Нетрудно это увидеть

fmap extract . duplicate = id

поскольку duplicate украшает каждый узел своим контекстом, тогда fmap extract отбрасывает украшение.

Обратите внимание, что fmap получает доступ только к меткам ввода для вычисления меток вывода. Предположим, мы хотим вычислить выходные метки в зависимости от каждой входной метки в ее контексте? Например, для немаркированного дерева мы можем захотеть пометить каждый узел размером всего его поддерева. Благодаря экземпляру Foldable для Cofree f мы должны иметь возможность подсчитывать узлы с помощью.

length :: Foldable f => Cofree f a -> Int

Так что это означает

fmap length . duplicate :: Cofree f a -> Cofree f Int

Ключевая идея комонад заключается в том, что они захватывают «вещи с некоторым контекстом» и позволяют вам везде применять контекстно-зависимые карты.

extend :: Comonad c => (c a -> b) -> c a -> c b
extend f = fmap f       -- context-dependent map everywhere
           .            -- after
           duplicate    -- decorating everything with its context

Более прямое определение extend избавит вас от необходимости дублирования (хотя это означает только совместное использование).

extend :: (Cofree f a -> b) -> Cofree f a -> Cofree f b
extend g ca@(_ :< fca) = g ca :< fmap (extend g) fca

И вы можете вернуть duplicate, взяв

duplicate = extend id -- the output label is the input label in its context

Более того, если вы выберете extract для каждой метки в контексте, вы просто вернете каждую метку туда, откуда она пришла:

extend extract = id

Эти «операции над метками в контексте» называются «совместными стрелками Клейсли».

g :: c a -> b

и работа extend состоит в том, чтобы интерпретировать стрелку со-Клейсли как функцию на целых структурах. Операция extract представляет собой тождественную стрелку Клейсли и интерпретируется extend как функция тождества. Конечно, есть со-композиция Клейсли

(=<=) :: Comonad c => (c s -> t) -> (c r -> s) -> (c r -> t)
(g =<= h) = g . extend h

а законы комонады гарантируют, что =<= является ассоциативным и поглощает extract, давая нам категорию ко-Клейсли. Более того, у нас есть

extend (g =<= h)  =  extend g . extend h

так что extend является функтором (в категориальном смысле) из категории ко-Клейсли в множества-и-функции. Эти законы нетрудно проверить для Cofree, так как они следуют из Functor законов для формы узла.

Теперь, один полезный способ увидеть структуру в cofree comonad - это своего рода "игровой сервер". Структура

a :< fca

представляет состояние игры. Ход в игре состоит либо из «остановки», и в этом случае вы получаете a, либо из «продолжения», выбирая поддерево fca. Например, рассмотрим

Cofree ((->) move) prize

Клиент для этого сервера должен либо остановиться, либо продолжить работу, указав move: это список из moves. Игра проводится следующим образом:

play :: [move] -> Cofree ((->) move) prize -> prize
play []       (prize :< _) = prize
play (m : ms) (_     :< f) = play ms (f m)

Возможно, move — это Char, а prize — результат разбора последовательности символов.

Если вы внимательно посмотрите, то увидите, что [move] — это версия Free ((,) move) (). Бесплатные монады представляют клиентские стратегии. Функтор ((,) move) представляет собой командный интерфейс только с командой «отправить move». Функтор ((->) move) представляет собой соответствующую структуру «ответить на отправку move».

Некоторые функторы можно рассматривать как захват командного интерфейса; свободная монада для такого функтора представляет программы, выполняющие команды; у функтора будет «двойник», который представляет, как реагировать на команды; сосвободная комонада двойного числа — это общее понятие среды, в которой могут выполняться программы, выполняющие команды, с меткой, говорящей, что делать, если программа останавливается и возвращает значение, и подструктурами, говорящими, как продолжить выполнение программы, если он выдает команду.

Например,

data Comms x = Send Char x | Receive (Char -> x)

описывает, что разрешено отправлять или получать символы. Его двойственность

data Responder x = Resp {ifSend :: Char -> x, ifReceive :: (Char, x)}

В качестве упражнения посмотрите, сможете ли вы реализовать взаимодействие

chatter :: Free Comms x -> Cofree Responder y -> (x, y)
person pigworker    schedule 07.08.2016
comment
Подробнее об аннигиляции материи и антиматерии в блоге Кметта< /а> - person Benjamin Hodgson♦; 08.08.2016
comment
@pigworker Для того, чтобы бесплатная отмена работала, требуется ли какое-то конкретное свойство из категории, в которой живут функторы? - person danidiaz; 08.08.2016
comment
офигенно из-за data Cofree nodeOf label = label :< nodeOf (Cofree nodeOf label), объясняющего завязки на со-клейсли стрелы и складные, а также мотивирующий пример игры (плюс полезное упражнение в конце). Спасибо! - person Josh.F; 08.08.2016
comment
@danidiaz Кажется, ты на что-то намекаешь. Просто скажи, что ты имеешь в виду. На мой взгляд, Haskell дает мне (слишком много, но по крайней мере) достаточно семантики для запуска чистых тотальных программ в категории множеств и функций. В этой ситуации важно отличать индуктивные типы (такие как свободные монады: у клиента завершающая миссия) от коиндуктивных типов (таких как сосвободные сомонады: сервер должен оставаться там столько времени, сколько хочет клиент). Мой препроцессор позволяет мне писать codata в качестве ключевого слова для документации. - person pigworker; 09.08.2016
comment
@wheaties Не понимаю, почему бы и нет. Это может занять некоторое время, чтобы обойти это. - person pigworker; 18.08.2016