Je ne sais pas si cette suggestion est "bonne" pour les définitions sophistiquées du "bon", mais elle est facile et amusante. Je fais souvent un exercice consistant à écrire le noyau d'un éditeur de texte en Haskell, en le liant au code de rendu que je fournis. Le modèle de données est le suivant.
Tout d'abord, je définis ce que c'est qu'un curseur à l'intérieur d'une liste de x
-éléments, où l'information disponible au curseur est d'un certain type m
. (Le x
s'avérera être Char
o String
.)
type Cursor x m = (Bwd x, m, [x])
Este Bwd
est juste l'arrière "snoc-lists". Je veux garder de fortes intuitions spatiales, donc je tourne les choses dans mon code, pas dans ma tête. L'idée est que les éléments les plus proches du curseur sont les plus facilement accessibles. C'est l'esprit de la fermeture éclair.
data Bwd x = B0 | Bwd x :< x deriving (Show, Eq)
Je fournis un type singleton gratuit pour agir comme un marqueur lisible pour le curseur...
data Here = Here deriving Show
...et je peux donc dire ce que c'est que d'être quelque part dans une String
type StringCursor = Cursor Char Here
Maintenant, pour représenter un tampon de plusieurs lignes, nous avons besoin de String
au-dessus et au-dessous de la ligne où se trouve le curseur, et d'une touche StringCursor
au milieu, pour la ligne que nous sommes en train d'éditer.
type TextCursor = Cursor String StringCursor
Este TextCursor
est tout ce que j'utilise pour représenter l'état du tampon d'édition. C'est une fermeture éclair à deux couches. Je fournis aux étudiants du code pour rendre une fenêtre d'affichage sur le texte dans une fenêtre shell compatible avec l'escape ANSI, en veillant à ce que la fenêtre d'affichage contienne le curseur. Tout ce qu'ils ont à faire est d'implémenter le code qui met à jour le tampon d'édition. TextCursor
en réponse à des frappes au clavier.
handleKey :: Key -> TextCursor -> Maybe (Damage, TextCursor)
donde handleKey
devrait retourner Nothing
si la frappe est dénuée de sens, mais sinon, délivrer Just
une mise à jour TextCursor
et un "rapport d'avarie", ce dernier étant l'un des documents les plus importants de l'Union européenne.
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)
(Si vous vous demandez quelle est la différence entre renvoyer Nothing
et renvoyant Just (NoChange, ...)
si vous souhaitez également que l'éditeur émette un bip). Le rapport de dommages indique au moteur de rendu la quantité de travail qu'il doit effectuer pour mettre à jour l'image affichée.
El Key
donne simplement une représentation lisible des données pour les frappes possibles, en faisant abstraction des séquences d'échappement ANSI brutes. Ce n'est pas remarquable.
Je donne aux étudiants un indice important sur la façon de monter et descendre avec ce modèle de données en leur proposant ces éléments :
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)
El deactivate
est utilisée pour déplacer le focus hors d'un Cursor
en vous donnant une liste ordinaire, mais en vous indiquant où se trouve le curseur. était . La valeur correspondante activate
tente de placer le curseur à une position donnée dans une liste :
activate :: (Int, [x]) -> Cursor x Here
activate (i, xs) = inward i (B0, Here, xs) where
inward _ c@(_, 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!
Je propose aux étudiants une définition délibérément incorrecte et incomplète de 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
qui se contente de traiter les frappes de caractères ordinaires, mais qui fait en sorte que le texte apparaisse à l'envers. Il est facile de voir que le caractère c
apparaît droite de Here
. Je les invite à corriger le bogue et à ajouter des fonctionnalités pour les touches fléchées, le retour arrière, la suppression, le retour, etc.
Ce n'est peut-être pas la représentation la plus efficace qui soit, mais elle est purement fonctionnelle et permet au code de se conformer concrètement à nos intuitions spatiales concernant le texte en cours d'édition.