Как насчёт продвижения вглубь файловой системы? Если мы находимся в "root"
и хотим сфокусироваться на файле "dijon_poupon. doc"
, оставляемая нами «хлебная крошка» будет включать имя "root"
вместе с элементами, предшествующими файлу "dijon_poupon.doc"
, и элементами, идущими за ним. Вот функция, которая, получив имя, фокусируется на файле или каталоге, расположенном в текущем каталоге, куда в текущий момент наведён фокус:
import Data.List (break)
fsTo :: Name –> FSZipper –> FSZipper
fsTo name (Folder folderName items, bs) =
let (ls, item:rs) = break (nameIs name) items
in (item, FSCrumb folderName ls rs:bs)
nameIs :: Name –> FSItem –> Bool
nameIs name (Folder folderName _) = name == folderName
nameIs name (File fileName _) = name == fileName
Функция fsTo
принимает значения Name
и FSZipper
и возвращает новое значение FSZipper
, которое фокусируется на файле с заданным именем. Этот файл должен присутствовать в текущем каталоге, находящемся в фокусе. Данная функция не производит поиск везде – она просто смотрит в текущем каталоге.
Сначала мы используем функцию break
, чтобы разбить список элементов в каталоге на те, что предшествуют искомому нами файлу, и те, что идут за ним. Функция break
принимает предикат и список и возвращает пару списков. Первый список в паре содержит элементы, для которых предикат возвращает значение False
. Затем, когда предикат возвращает значение True
для элемента, функция помещает этот элемент и остальную часть списка во второй элемент пары. Мы создали вспомогательную функцию nameIs
, которая принимает имя и элемент файловой системы и, если имена совпадают, возвращает значение True
.
Теперь ls
– список, содержащий элементы, предшествующие искомому нами элементу; item
является этим самым элементом, а rs
– это список элементов, идущих за ним в его каталоге. И вот сейчас, когда они у нас есть, мы просто представляем элемент, полученный нами из функции break
, как фокус и строим «хлебную крошку», которая содержит все необходимые ей данные.
Обратите внимание, что если имя, которое мы ищем, не присутствует в каталоге, образец item:rs
попытается произвести сопоставление с пустым списком, и мы получим ошибку. А если наш текущий фокус – файл, а не каталог, мы тоже получим ошибку, и программа завершится аварийно.
Итак, мы можем двигаться вверх и вниз по нашей файловой системе. Давайте начнём движение с корня и перейдём к файлу "skull_man(scary).bmp"
:
ghci> let newFocus = (myDisk, []) -: fsTo "pics" -: fsTo "skull_man(scary).bmp"
Значение newFocus
теперь – застёжка, сфокусированная на файле skull_man(scary).bmp
. Давайте получим первый компонент застёжки (сам фокус) и посмотрим, так ли это на самом деле.
ghci> fst newFocus
File "skull_man(scary).bmp" "Ой!"
Переместимся выше и сфокусируемся на соседнем с ним файле "watermelon_smash.gif"
:
ghci> let newFocus2 = newFocus –: fsUp –: fsTo "watermelon_smash.gif"
ghci> fst newFocus2
File "watermelon_smash.gif" "шмяк!!"
Теперь, когда мы можем передвигаться по нашей файловой системе, ею легко манипулировать. Вот функция, которая переименовывает находящийся в данный момент в фокусе файл или каталог:
fsRename :: Name –> FSZipper –> FSZipper
fsRename newName (Folder name items, bs) = (Folder newName items, bs)
fsRename newName (File name dat, bs) = (File newName dat, bs)
Давайте переименуем наш каталог "pics"
в "cspi"
:
ghci> let newFocus = (myDisk, []) –: fsTo "pics" –: fsRename "cspi" –: fsUp
Мы спустились к каталогу "pics"
, переименовали его, а затем поднялись обратно вверх.
Как насчёт функции, которая создаёт новый элемент в текущем каталоге? Встречайте:
fsNewFile :: FSItem –> FSZipper –> FSZipper
fsNewFile item (Folder folderName items, bs) =
(Folder folderName (item:items), bs)