Как работает ArrowLoop? Кроме того, mfix? - PullRequest
29 голосов
/ 08 августа 2011

Мне довольно комфортно с остальными механизмами стрелок, но я не понимаю, как работает цикл.Это кажется мне волшебным, и это плохо для моего понимания.У меня также есть проблемы с пониманием 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 Ответ

25 голосов
/ 08 августа 2011

В этом коде ключевой частью является стрелка delay 0 в блоке rec.Чтобы увидеть, как это работает, полезно думать о значениях, которые меняются во времени и по времени, а также нарезаются на кусочки.Я считаю ломтики «днями».Блок rec объясняет, как работают вычисления на каждый день.Он организован по значению , а не по причинному порядку , но мы все равно можем отслеживать причинность, если будем осторожны.Важно отметить, что мы должны удостовериться (без какой-либо помощи со стороны types ), что работа каждого дня зависит от прошлого, а не от будущего.Однодневный 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 устанавливает такой порядок.

Имейте в виду, однако, что система типов Хаскелла очень мало помогает в обеспечении существования такого порядка.Вы можете использовать петли для полной ерунды!Так что твой вопрос далеко не тривиален.Каждый раз, когда вы читаете или пишете такую ​​программу, вам нужно подумать: «Как это может сработать?».Вам необходимо проверить, что delay (или аналогичный) используется надлежащим образом, чтобы гарантировать, что информация требуется только тогда, когда она может быть вычислена.Обратите внимание, что конструкторы , особенно (:), также могут действовать как задержки: нет ничего необычного в том, чтобы вычислять хвост списка, по-видимому, учитывая весь список (но соблюдая осторожность только для осмотра головы).В отличие от императивного программирования, ленивый функциональный стиль позволяет вам организовать свой код вокруг концепций, отличных от последовательности событий, но это свобода, которая требует более тонкого понимания времени.

...