2016-12-20 2 views
3

Je plonge toujours plus profondément dans les lentilles de Kmett; Aujourd'hui, j'essaye d'écrire des parcours personnalisés, jusqu'à présent j'ai réussi à composer en créant des traversées existantes pour en créer de nouvelles, mais je fais quelque chose d'un peu plus compliqué et je suis coincé. J'écris un éditeur de texte et j'ajoute juste des curseurs multiples, à l'origine chaque tampon avait un curseur et avait un objectif pour le mettre au point, maintenant je généraliser à une liste de curseurs et je veux une traversée sur le liste. L'astuce est que dans le cas précédent mon objectif a fait une validation à l'intérieur du setter pour s'assurer qu'un curseur a été serré pour tenir dans la plage valide du texte du tampon. Il ressemblait à ceci:Rédaction d'une Traversée plus complexe (Lentilles)

clampCursor :: Text -> Cursor -> Cursor 

cursor :: Lens' Buffer Cursor 
cursor = lens getter setter 
    where getter buf = buf^.curs 
     setter buf new = let txt = buf^.text 
          in buf & curs .~ clampCursor txt new 

Notez comment il utilise les informations de texte du contexte du tampon pour créer une lentille sur le curseur; (Aussi, j'aimerais entendre parler de façons plus propres de faire cela au lieu de faire des lentilles personnalisées si quelqu'un a des suggestions, je me suis retrouvé à le faire beaucoup). Maintenant que j'ai plusieurs curseurs, j'ai besoin de le transformer en Traversal ', mais bien sûr je ne peux pas définir de traversée en utilisant la méthode lens getter setter; En regardant autour de la façon de définir traversals je lis ceci tutorial; Lequel des états suivants:

Question: Comment créer des traversées?

Réponse: Il existe trois principales façons de créer traversals primitifs:

  • traverse est un Traversal » que vous obtenez pour tout type qui implémente Traversable
  • Chaque Lens sera également de type vérification comme Traversal «
  • Vous pouvez utiliser le modèle Haskell pour générer en utilisant les makePrisms de Traversal puisque chaque prisme » est aussi un Traversal »(non couvert dans ce tutoriel)

Aucune de ces méthodes n'aide vraiment ici; J'ai aussi vu le style où vous créez une traversée en utilisant un style applicatif, mais ça m'a toujours dérouté et je ne sais pas vraiment comment je l'utiliserais dans ce cas pour obtenir ce que je veux. Je suppose que je pourrais écrire un Lens' Buffer [Cursor] qui mappe sur les curseurs dans le setter pour effectuer la validation et ensuite traverser cette liste, mais je pense qu'il doit y avoir un moyen de le faire dans la traversée APRÈS la traversée (quand chaque l'élément unique est focalisé) d'une manière ou d'une autre. Peut-être qu'il y a une meilleure façon de le faire entièrement;

Toujours à la recherche de tout ce que je peux sur les traversées, donc toutes les explications sont appréciées! Merci!

EDIT: @dfeuer a souligné que lorsque vous faites ce genre de validation vous vous retrouvez avec des lentilles non valides, j'aime vraiment l'interface propre qu'il fournit pour les faire à l'intérieur de l'objectif; et pour autant que je le sache, puisque la validation est idempotente, cela ne devrait pas causer de problème réel, mais je suis ouvert à des suggestions sur la façon de faire mieux.

+1

Vous avez déjà un peu d'un problème: grâce au serrage, votre 'Lens 'n'est pas tout à fait une lentille respectueuse de la loi. Pouvez-vous donner plus d'informations sur ce qu'est un 'Cursor' et comment est-il utilisé? Votre configuration actuelle me semble un peu étrange. Je n'ai pas tendance à penser qu'un curseur fait partie d'un tampon. Je soupçonne que ce que vous voulez vraiment, c'est quelque chose de plus comme une collection de "pointeurs" ressemblant à des lentilles dans un tampon. – dfeuer

+0

Hrmm, je suppose que c'est vrai; Y a-t-il une meilleure façon de faire la validation là où l'interface est propre mais respectueuse des lois? J'ai toujours pensé aux setters comme où la validation devrait être effectuée. Fondamentalement, un curseur est un décalage dans le fichier qui conserve la trace de l'endroit où les opérations futures doivent être effectuées, par ex. supprimer des caractères, insérer du texte, etc. "Buffer" dans ce cas fait référence à la terminologie Vim d'une représentation de texte éditable. Dans mon cas, chaque tampon contient un ensemble de curseurs.J'aimerais faire tout en utilisant des pointeurs de type objectif, mais vous enfreignez d'autres lois de l'objectif lorsque vous essayez de le faire: P –

+0

Je voudrais également faire plus de transformations sur la liste de curseurs quand elle est définie; par exemple, je voudrais les trier et supprimer les doublons aussi bien. –

Répondre

2

Ma recommandation est d'utiliser la représentation Functor/Applicative de lentilles directement pour faire ce genre de chose.

Pour écrire un changement non type (simple) ou un LensTraversal, vous devez écrire une fonction qui prend comme arguments une fonction k :: a -> f a et de votre structure s et produit alors un f s.

k est une sorte de fonction de modification généralisée, qui représente le changement qu'un utilisateur de l'objectif veut apporter aux données focalisées par l'objectif. Mais parce que k n'est pas simplement de type a -> a, mais au lieu du type a -> f a, il permet également de porter un "résultat" hors de la mise à jour, par exemple, la valeur du champ avant sa mise à jour (si vous utilisez la monade d'état pour f, vous pouvez définir l'état de l'ancienne valeur du champ dans la fonction de mise à jour et le lire lorsque vous exécutez la monade d'état par la suite).

Notre approche dans le code suivant est de changer cette fonction de modification, d'effectuer un certain serrage avant de retourner la nouvelle valeur:

-- Takes a cursor update function and returns a modified update function 
-- that clamps the return value of the original function 
clampCursorUpdate :: Functor f => Text -> (Cursor -> f Cursor) -> (Cursor -> f Cursor) 
clampCursorUpdate k = \cur -> fmap (clampCursor txt) (k cur) 

On peut alors tourner une lentille non-validation dans un objectif de validation (notez que comme dit dans les commentaires, cet objectif de valideuse est un objectif respectueux de la loi):

-- assuming that _cursor is a lens that accesses 
-- the _cursor field without doing any validation 
_cursor :: Lens' Buffer Cursor 

cursor :: Functor f => (Cursor -> f Cursor) -> Buffer -> f Buffer 
cursor k buffer = _cursor (clampCursorUpdate txt k) buffer 
where txt = buffer^.text 

cette approche est facile de généraliser à traversals. Tout d'abord, nous écrivons l'traversal non-validation en composant un Lens' Buffer [Cursor] avec traverse, qui le transformer en un Traversal' Buffer Cursor:

-- assuming that _cursors is a lens that returns the list of cursors 
_cursors :: Lens' Buffer [Cursor] 

-- non-validating cursors traversal 
_cursorsTraversal :: Traversal' Buffer Cursor 
_cursorsTraversal = _cursors . traverse 

Maintenant, nous pouvons utiliser la même approche que nous avons fait plus tôt: depuis le traversal fait déjà le " cartographie » pour nous, le code est le même, sauf que nous avons maintenant une contrainte Applicative f sur notre f, parce que nous voulons un Traversal':

cursors :: Applicative f => (Cursor -> f Cursor) -> Buffer -> f Buffer 
cursors k buffer = _cursorsTraversal (clampCursorUpdate txt k) buffer 
whee txt = buffer^.text 
0

Comme curs :: Lens' Buffer Cursor devient curs :: Lens' Buffer [Cursor], votre tâche de construire une loi sans tenir compte cursor :: Traversal' Buffer Cursor peut être divisé en la construction d'une loi sans tenir compte Lens' Buffer [Cursor] qui fait des contrôles hors limites, et en tournant toute Lens' s [a] en Traversal' s a.

La première aurait pu être résolue par vous: Vous faites ce que vous avez déjà fait, mais vérifiez les limites sur chaque élément du [Cursor].

Le second est une demande respectueux de la loi et vous pourriez donc avoir attendu une réponse sans ce torrent de commentaires sous votre message:

turnIntoTraversal :: Lens' s [a] -> Traversal' s a 
turnIntoTraversal l = l . traverse 

aussi pour mon plaisir est ici une tentative de mise en œuvre directe.

cursor :: Traversal' Buffer Cursor 
cursor atofa s = (curs . traverse) (fmap (clampCursor $ s ^. text) . atofa) s