Что было бы хорошими чисто функциональными структурами данных для текстовых редакторов? Я хочу иметь возможность вставлять отдельные символы в текст и удалять отдельные символы из текста с приемлемой эффективностью, и я хотел бы иметь возможность хранить старые версии, поэтому я могу легко отменить изменения.
Должен ли я просто использовать список строк и повторно использовать строки, которые не меняются от версии к версии?
Ответ 2
Я не знаю, является ли это предложение "хорошим" для сложных определений "хорошего", но это легко и весело. Я часто задал упражнение, чтобы написать ядро текстового редактора в Haskell, связавшись с кодом рендеринга, который я предоставляю. Модель данных выглядит следующим образом.
Сначала я определяю, что это должен быть курсор внутри списка x
-элементов, где информация, доступная в курсоре, имеет некоторый тип m
. (x
окажется Char
или String
.)
type Cursor x m = (Bwd x, m, [x])
Эта Bwd
вещь - это только отсталые "snoc-lists". Я хочу сохранить сильные пространственные интуиции, поэтому я перехожу в своем коде, а не в свою голову. Идея состоит в том, что материал, ближайший к курсору, наиболее легко доступен. Это дух "Застежки-молнии".
data Bwd x = B0 | Bwd x :< x deriving (Show, Eq)
Я предоставляю бесплатный одноэлементный тип, чтобы действовать как читаемый маркер для курсора...
data Here = Here deriving Show
... и я могу сказать, что это должно быть где-то в String
type StringCursor = Cursor Char Here
Теперь, чтобы представить буфер из нескольких строк, нам нужно String
выше и ниже строки с курсором и StringCursor
в середине, для строки, которую мы сейчас редактируем.
type TextCursor = Cursor String StringCursor
Этот тип TextCursor
используется для представления состояния буфера редактирования. Это двухслойная молния. Я предоставляю студентам код для визуализации окна просмотра в тексте в окне оболочки с поддержкой ANSI, гарантируя, что область просмотра содержит курсор. Все, что им нужно сделать, это реализовать код, который обновляет TextCursor
в ответ на нажатия клавиш.
handleKey :: Key -> TextCursor -> Maybe (Damage, TextCursor)
где handleKey
должен возвращать Nothing
, если нажатие клавиши бессмысленно, но в противном случае доставить Just
обновленный TextCursor
и "отчет об ущербе", последний из которых является одним из
data Damage
= NoChange -- use this if nothing at all happened
| PointChanged -- use this if you moved the cursor but kept the text
| LineChanged -- use this if you changed text only on the current line
| LotsChanged -- use this if you changed text off the current line
deriving (Show, Eq, Ord)
(Если вам интересно, какая разница между возвратом Nothing
и возвратом Just (NoChange, ...)
, подумайте, хотите ли вы, чтобы редактор издавал звуковой сигнал.) Отчет о повреждении сообщает рендереру, сколько работы ему нужно сделать для обновите отображаемое изображение.
Тип Key
просто дает читаемое представление dataype для возможных нажатий клавиш, абстрагируясь от необработанных последовательностей ANSI. Это неприметно.
Я предоставляю студентам большой ключ к тому, чтобы идти вверх и вниз с этой моделью данных, предлагая эти части набора:
deactivate :: Cursor x Here -> (Int, [x])
deactivate c = outward 0 c where
outward i (B0, Here, xs) = (i, xs)
outward i (xz :< x, Here, xs) = outward (i + 1) (xz, Here, x : xs)
Функция deactivate
используется для смещения фокуса из Cursor
, что дает вам обычный список, но сообщает вам, где находился курсор. Соответствующая функция activate
пытается поместить курсор в заданную позицию в список:
activate :: (Int, [x]) -> Cursor x Here
activate (i, xs) = inward i (B0, Here, xs) where
inward _ [email protected](_, Here, []) = c -- we can go no further
inward 0 c = c -- we should go no further
inward i (xz, Here, x : xs) = inward (i - 1) (xz :< x, Here, xs) -- and on!
Я предлагаю учащимся сознательно неправильное и неполное определение handleKey
handleKey :: Key -> TextCursor -> Maybe (Damage, TextCursor)
handleKey (CharKey c) (sz,
(cz, Here, cs),
ss)
= Just (LineChanged, (sz,
(cz, Here, c : cs),
ss))
handleKey _ _ = Nothing
который просто обрабатывает нажатия клавиш обычного характера, но делает текст выходящим назад. Легко видеть, что символ c
отображается справа от Here
. Я предлагаю им исправить ошибку и добавить функциональность для клавиш со стрелками, возврата, удаления, возврата и т.д.
Это может быть не самое эффективное представление когда-либо, но оно чисто функционально и позволяет коду конкретно соответствовать нашим пространственным интуициям об редактируемом тексте.