> guard (c' `elem` [1..8] && r' `elem` [1..8])
> return (c',r')
Конь всегда может перемещаться на одну клетку горизонтально или вертикально и на две клетки вертикально или горизонтально, причём каждый его ход включает движение и по горизонтали, и по вертикали. Пара >(c', r')
получает каждое значение из списка перемещений, а затем функция >guard
заботится о том, чтобы новый ход, а именно пара >(c', r')
, был в пределах доски. Если движение выходит за доску, она возвращает пустой список, что приводит к неудаче, и вызов >return (c', r')
не обрабатывается для данной позиции.
Эта функция может быть записана и без использования списков в качестве монад. Вот как записать её с использованием функции >filter
:
>moveKnight :: KnightPos –> [KnightPos]
>moveKnight (c,r) = filter onBoard
> [(c+2,r-1),(c+2,r+1),(c-2,r-1),(c-2,r+1)
> ,(c+1,r-2),(c+1,r+2),(c-1,r-2),(c-1,r+2)
> ]
> where onBoard (c,r) = c `elem` [1..8] && r `elem` [1..8]
Обе версии делают одно и то же, так что выбирайте ту, которая кажется вам лучше. Давайте опробуем функцию:
>ghci> moveKnight (6, 2)
>[(8,1),(8,3),(4,1),(4,3),(7,4),(5,4)]
>ghci> moveKnight (8, 1)
>[(6,2),(7,3)]
Работает чудесно! Мы берём одну позицию и просто выполняем все возможные ходы сразу, так сказать.
Поэтому теперь, когда у нас есть следующая недетерминированная позиция, мы просто используем операцию >>>=
, чтобы передать её функции >moveKnight
. Вот функция, принимающая позицию и возвращающая все позиции, которые вы можете достигнуть из неё в три хода:
>in3 :: KnightPos –> [KnightPos]
>in3 start = do
> first <– moveKnight start
> second <– moveKnight first
> moveKnight second
Если вы передадите ей пару >(6, 2)
, результирующий список будет довольно большим. Причина в том, что если есть несколько путей достигнуть определённой позиции в три хода, ход неожиданно появляется в списке несколько раз.
Вот предшествующий код без использования нотации >do
:
>in3 start = return start >>= moveKnight >>= moveKnight >>= moveKnight
Однократное использование операции >>>=
даёт нам все возможные ходы с начала. Когда мы используем операцию >>>=
второй раз, то для каждого возможного первого хода вычисляется каждый возможный следующий ход; то же самое верно и в отношении последнего хода.
Помещение значения в контекст по умолчанию с применением к нему функции >return
, а затем передача его функции с использованием операции >>>=
– то же самое, что и обычное применение функции к данному значению; но мы сделали это здесь, во всяком случае, ради стиля.
Теперь давайте создадим функцию, которая принимает две позиции и сообщает нам, можем ли мы попасть из одной в другую ровно в три хода: