Изучай Haskell во имя добра! (Липовача) - страница 289

В этом случае «хлебная крошка» должна быть похожа на каталог – только выбранный нами в данный момент каталог должен в нём отсутствовать. Вы спросите: «А почему не на файл?» Ну, потому что, когда мы фокусируемся на файле, мы не можем углубляться в файловую систему, а значит, не имеет смысла оставлять «хлебную крошку», которая говорит, что мы пришли из файла. Файл – это что-то вроде пустого дерева.

Если мы фокусируемся на каталоге >"root", а затем на файле >"dijon_poupon.doc", как должна выглядеть «хлебная крошка», которую мы оставляем? Она должна содержать имя своего родительского каталога вместе с элементами, идущими перед файлом, на котором мы фокусируемся, и следом за ним. Поэтому всё, что нам требуется, – значение >Name и два списка элементов. Храня два отдельных списка для элементов, идущих перед элементом, на котором мы фокусируемся, и для элементов, идущих за ним, мы будем точно знать, где мы его поместили, при перемещении обратно вверх. Таким образом, нам известно местоположение отверстия.

Вот наш тип «хлебной крошки» для файловой системы:

>data FSCrumb = FSCrumb Name [FSItem] [FSItem]

>deriving (Show)

А вот синоним типа для нашей застёжки:

>type FSZipper = (FSItem, [FSCrumb])

Идти обратно вверх по иерархии очень просто. Мы берём самую последнюю «хлебную крошку» и собираем новый фокус из текущего фокуса и «хлебной крошки» следующим образом:

>fsUp :: FSZipper –> FSZipper

>fsUp (item, FSCrumb name ls rs:bs) = (Folder name (ls ++ [item] ++ rs), bs)

Поскольку нашей «хлебной крошке» были известны имя родительского каталога, а также элементы, которые шли перед находящимся в фокусе элементом каталога (то есть >ls), и элементы, которые шли за ним (то есть >rs), перемещаться вверх было легко.

Как насчёт продвижения вглубь файловой системы? Если мы находимся в >"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 и возвращает новое значение