Как работает ArrowLoop? Кроме того, mfix?
Теперь я чувствую себя комфортно с остальными механизмами стрелок, но я не понимаю, как работает петля. Это кажется мне волшебным, и это плохо для моего понимания. У меня также есть проблемы с пониманием mfix. Когда я смотрю на кусок кода, который использует rec
в proc
или же do
блок, я запутался. С помощью обычного монадического кода или кода со стрелкой я могу выполнять вычисления и сохранять оперативную картину того, что происходит в моей голове. Когда я доберусь до rec
Я не знаю, какую фотографию оставить! Я застрял, и я не могу рассуждать о таком коде.
Пример, который я пытаюсь найти, взят из статьи Росса Патерсона о стрелках, которая посвящена цепям.
counter :: ArrowCircuit a => a Bool Int
counter = proc reset -> do
rec output <- returnA -< if reset then 0 else next
next <- delay 0 -< output+1
returnA -< output
Я предполагаю, что если я пойму этот пример, я смогу понять цикл в целом, и он станет отличным способом понимания mfix. Они чувствуют, по сути, то же самое для меня, но, возможно, есть тонкость, которую я скучаю? В любом случае, что я действительно ценю, так это оперативную картину таких фрагментов кода, так что я могу просматривать их в своей голове, как "обычный" код.
Изменить: Благодаря ответу Pigworker, я начал думать о rec и таких, как требования выполняются. Принимая counter
Например, первая строка блока rec требует значения, называемого output
, Я представляю себе это как создание коробки с маркировкой output
и попросив блок rec заполнить это поле. Чтобы заполнить это поле, мы вводим значение в returnA, но само это значение требует другого значения, называемого next
, Чтобы использовать это значение, оно должно запрашиваться из другой строки в блоке записи, но пока не имеет значения, где в блоке записи оно требуется.
Итак, мы переходим к следующей строке, и мы находим поле с надписью next
и мы требуем, чтобы другие вычисления заполнили его. Теперь это вычисление требует нашего первого окна! Таким образом, мы даем ему поле, но оно не имеет значения внутри него, поэтому, если это вычисление требует содержимого output
Мы попали в бесконечный цикл. К счастью, задержка принимает коробку, но создает значение, не заглядывая внутрь коробки. Это заполняет next
, что затем позволяет нам заполнить output
, Теперь, когда output
заполняется, когда обрабатывается следующий вход этой схемы, предыдущий output
коробка будет иметь свою ценность, готовая потребовать для производства следующего next
и, таким образом, следующий output
,
Как это звучит?
1 ответ
В этом коде они ключевой delay 0
стрелка в rec
блок. Чтобы увидеть, как это работает, полезно думать о значениях, которые меняются во времени и по времени, а также нарезаются на кусочки. Я считаю ломтики "днями". rec
Блок объясняет, как работает каждый день. Он организован по значению, а не по причинному порядку, но мы все равно можем отслеживать причинность, если будем осторожны. Важно отметить, что мы должны убедиться (без какой-либо помощи со стороны типов), что каждый день работы зависит от прошлого, но не от будущего. Однодневный delay 0
Это дает нам время: он сдвигает свой входной сигнал на один день позже, заботясь о первом дне, передавая значение 0. Входной сигнал задержки - "завтрашний день". next
".
rec output <- returnA -< if reset then 0 else next
next <- delay 0 -< output+1
Итак, глядя на стрелки и их результаты, мы поставляем сегодня output
но завтра next
, Глядя на входные данные, мы полагаемся на сегодняшние reset
а также next
ценности. Ясно, что мы можем доставлять эти выходы из этих входов без перемещения во времени. output
сегодня next
номер, если мы reset
до 0; завтра next
номер является преемником сегодняшнего output
, Сегодняшние next
Таким образом, значение приходит со вчерашнего дня, если только вчера не было, в этом случае это 0.
На более низком уровне вся эта установка работает из-за лени Хаскелла. Haskell вычисляет по стратегии, ориентированной на спрос, поэтому, если существует последовательный порядок задач, который учитывает причинность, Haskell найдет его. Здесь delay
устанавливает такой порядок.
Имейте в виду, однако, что система типов Haskell очень мало помогает в обеспечении существования такого порядка. Вы можете использовать петли для полной ерунды! Так что твой вопрос далеко не тривиален. Каждый раз, когда вы читаете или пишете такую программу, вам нужно подумать: "Как это может сработать?". Вы должны проверить это delay
(или аналогичный) используется надлежащим образом, чтобы гарантировать, что информация требуется только тогда, когда она может быть вычислена. Обратите внимание, что конструкторы, особенно (:)
тоже может действовать как задержки: нет ничего необычного в том, чтобы вычислять хвост списка, очевидно, учитывая весь список (но будьте осторожны только для осмотра головы). В отличие от императивного программирования, ленивый функциональный стиль позволяет вам организовать свой код вокруг концепций, отличных от последовательности событий, но это свобода, которая требует более тонкого понимания времени.