29просмотров
16 марта 2024 г.
Score: 32
Сижу, читаю книгу Брагилевского "Haskell in Depth" и вот он предлагает самостоятельно разобраться в такой функции rotateMany: -- Сам пример показывает, как могла бы выглядеть
-- программа для управления радаром. -- Есть тип Direction, описывающий на какие стороны
-- может быть ориентирован радар.
data Direction = North | East | South | West deriving (Eq, Enum, Bounded, CyclicEnum, Show) -- Поворот Радара:
-- TNone - нет поворота
-- TLeft - налево
-- TRight - направо
-- TAround - вокруг, два раза налево.
data Turn = TNone | TLeft | TRight | TAround deriving (Eq, Enum, Bounded, Show) -- Функция rotate принимает тип поворота и направления,
-- а возвращает новое направление после поворота радара.
-- Для каждого типа представлены функции, которые описывают,
-- как обработать направление при определённом повороте:
-- при TNone применяется функция id, которая возвращает свой аргумент, т.е. принял North и вернули North, т.о. эмулируется отсутствие поворота;
-- при TLeft применяется функция cpred, которая возвращает предыдущий элемент перечисления в том, порядке, как они объявлены, приняли North, вернули West;
-- при TRight применяется функция csucc, которая возвращает следующий элемент перечисления в том, порядке, как они объявлены, приняли North, вернули East, если дошли до последнего, принял West, то вернёмся к началу и вернём North;
-- при TAround применяется функция cpred . cpred, которая возвращает предыдущий элемент на 2 шага, т.к. приняли North, вернули South;
rotate :: Turn -> Direction -> Direction
rotate TNone = id
rotate TLeft = cpred
rotate TRight = csucc
rotate TAround = cpred . cpred -- А вот как работает эта функция?
rotateMany :: Direction -> [Turn] -> Direction
rotateMany = foldl (flip rotate) Если в кратце, то foldl принимает некую функцию, стартовое значение (Direction), и то, что может быть свёрнуто (последовательно вычисленно), в нашем случае это список поворотов ([Turn]) и в результате функция вернёт полученное направление. Т.е. вот такой пример: foldl (flip rotate) North [TRight, TRight, TAround]
-- ^ список поворотов
-- ^ начальное значение
-- ^ функция, которая будет применяться. Начали в точке North, повернули направо (East), ещё раз направо (South), а потом кругом, т.е. 2 раза налево (North). flip rotate - это карированная функция (частично применённая, т.е. функция, часть аргументов которой инициализировали, и получили новую функцию, которая ожидает оставшихся аргументов), тип функции flip: flip :: (a -> b -> c) -> b -> a -> c Она принимает функцию 2 аргументов (a -> b -> c, a - первый аргумент, b - второй, c - результат) и ещё 2 аргумента в другом порядке по отношению к функции первого аргумента: -- Нормальное применение функции:
> rotate TRight North
East -- Аргументы перевёрнуты с помощью flip:
> (flip rotate) North TRight
East Если мы почитаем документацию по функции foldl, то мы найдём, как именно применяются аргументы при вычислении: foldl f z [x1, x2, ..., xn] == (...((z f x1) f x2) f...) f xn Исходя из этой подсказки, мы можем выполнить редукцию (вычисление) для следующего выражения: foldl (flip rotate) North [TRight, TRight, TAround] ==
((North flip rotate TRight) flip rotate TRight) flip rotate TAround ==
TAround rotate (TRight rotate (TRight rotate North)) ==
rotate TAround (rotate TRight (rotate TRight North)) ==
rotate TAround (rotate TRight East) ==
rotate TAround South ==
North