Одна из основных идей в функциональном программировании, чтобы выразить алгоритмы, как данных преобразований. На ленивом языке, таком как Haskell, мы можем пойти еще дальше и подумать о ленивых структурах данных как о reified вычислениях. В очень реальном смысле списки Haskell больше похожи на циклы, чем на обычные связанные списки: они могут быть вычислены постепенно и не обязательно должны существовать в памяти сразу. В то же время мы по-прежнему получаем много преимуществ наличия такого типа данных, как эта способность передавать его и проверять его с помощью сопоставления с образцом.
Учитывая это, «трюк» для выражения цикла for с индексом - это создать список всех значений, которые он может принять. Ваш пример, вероятно, самый простой случай: i
принимает все значения от 0
до 255
, поэтому мы можем использовать встроенные обозначения Хаскеля для диапазонов:
[0..255]
На высоком уровне, это эквивалентно Хаскелла из for (i = 0 to 255)
; мы можем затем выполнить фактическую логику в цикле, пройдя этот список либо рекурсивной функцией, либо функцией более высокого порядка из стандартной библиотеки. (Второй вариант является очень предпочтительным.)
Эта конкретная логика подходит для fold
. Сбрасывание позволяет нам взять элемент списка по элементу и создать какой-то результат. На каждом шаге мы получаем элемент списка и ценность нашего накопленного результата. В этом конкретном случае мы хотим обработать список слева направо, увеличивая индекс, поэтому мы можем использовать foldl
; одна сложная часть состоит в том, что она будет отображать список назад.
Вот тип foldl
:
foldl :: (b -> a -> b) -> b -> [a] -> b
Таким образом, наша функция принимает в нашем промежуточном значении и элемент списка и создает обновленный промежуточное значение. Поскольку мы создаем список и отслеживаем индекс, нашим промежуточным значением будет пара, содержащая оба. Тогда, как только мы получим окончательный результат, мы можем игнорировать значение idx
и реверса окончательный список, мы получаем:
a = let (result, _) = foldl step ([], 1) [0..255] in reverse result
where step (a, idx) i
| someCondition i = (idx:a, idx + 1)
| otherwise = (0:a, idx)
На самом деле, картина преобразования списка, сохраняя при этом какого-либо промежуточного состояния (idx
в этом случай) достаточно распространен, так что он имеет собственную функцию в терминах типа State
. Основная абстракция немного сложнее (прочитайте [«Вы могли бы изобрести монады»] [вы] для отличного введения), но полученный код на самом деле довольно приятно читать (за исключением импорта, я думаю: P) :
import Control.Applicative
import Control.Monad
import Control.Monad.State
a = evalState (mapM step [0..255]) 1
where step i
| someCondition i = get <* modify (+ 1)
| otherwise = return 0
идея заключается в том, что мы отображаем над [0..255]
, сохраняя при этом какого-либо государства (значение idx
) в фоновом режиме. evalState
- это то, как мы соединяем всю сантехнику и получаем окончательный результат. Функция step
применяется к каждому элементу списка элементов и может также получать доступ или изменять состояние.
Первый случай функции step
интересен. Оператор <*
сообщает, что сначала он делает вещь слева, вещь справа второй , но возвращает значение слева. Это позволяет нам получать текущее состояние, увеличивать его, но при этом возвращать полученное значение до было увеличено. Тот факт, что наше представление о состоянии является первоклассным сущностью, и мы можем иметь библиотечные функции, такие как <*
, очень мощное. Я нашел эту особую идиому, которая действительно полезна для обхода деревьев, а другие подобные идиомы были весьма полезны для другого кода.
Действительно хороший ответ. До этого момента я не знал о государственной монаде. Большое спасибо! – fuji