(Inspiré par ma réponse à cette question .)
Considérez ce code (il est censé trouver le plus grand élément inférieur ou égal à une entrée donnée):
data TreeMap v = Leaf | Node Integer v (TreeMap v) (TreeMap v) deriving (Show, Read, Eq, Ord)
closestLess :: Integer -> TreeMap v -> Maybe (Integer, v)
closestLess i = precise Nothing where
precise :: Maybe (Integer, v) -> TreeMap v -> Maybe (Integer, v)
precise closestSoFar Leaf = closestSoFar
precise closestSoFar (Node k v l r) = case i `compare` k of
LT -> precise closestSoFar l
EQ -> Just (k, v)
GT -> precise (Just (k, v)) r
Ce n'est pas très paresseux. Une fois le GT
cas entré, nous savons avec certitude que la valeur de retour finale sera Just
quelque chose plutôt que Nothing
, mais le Just
reste n'est pas disponible avant la fin. Je voudrais rendre ce paresseux afin que le Just
soit disponible dès que le GT
cas est entré. Mon cas de test pour cela est que je veux Data.Maybe.isJust $ closestLess 5 (Node 3 () Leaf undefined)
évaluer True
plutôt que d'enfoncer. Voici une façon dont je peux penser à faire ceci:
data TreeMap v = Leaf | Node Integer v (TreeMap v) (TreeMap v) deriving (Show, Read, Eq, Ord)
closestLess :: Integer -> TreeMap v -> Maybe (Integer, v)
closestLess _ Leaf = Nothing
closestLess i (Node k v l r) = case i `compare` k of
LT -> closestLess i l
EQ -> Just (k, v)
GT -> Just (precise (k, v) r)
where
precise :: (Integer, v) -> TreeMap v -> (Integer, v)
precise closestSoFar Leaf = closestSoFar
precise closestSoFar (Node k v l r) = case i `compare` k of
LT -> precise closestSoFar l
EQ -> (k, v)
GT -> precise (k, v) r
Cependant, je me répète maintenant: la logique de base est maintenant à la fois closestLess
dans et dans precise
. Comment puis-je écrire ceci pour qu'il soit paresseux mais sans me répéter?
la source
À partir de mon implémentation non paresseuse, j'ai d'abord refactorisé
precise
pour recevoirJust
comme argument, et généralisé son type en conséquence:Ensuite, je l'ai changé pour le faire
wrap
tôt et je m'appelle avecid
dans leGT
cas:Cela fonctionne toujours comme avant, sauf au profit de la paresse supplémentaire.
la source
id
s au milieu entreJust
et la finale sont-ils(k,v)
éliminés par le compilateur? probablement pas, les fonctions sont censées être opaques, et vous auriez pu (de manière faisable) utiliser à lafirst (1+)
place deid
tout ce que le compilateur sait. mais cela fait un code compact ... bien sûr, mon code est le démêlage et la spécification du vôtre ici, avec la simplification supplémentaire (l'élimination desid
s). aussi très intéressant de voir comment le type plus général sert de contrainte, une relation entre les valeurs impliquées (pas assez serrées cependant, avec l'first (1+)
autorisation dewrap
).precise
est utilisé à deux types, correspondant directement aux deux fonctions spécialisées utilisées dans la variante la plus verbeuse. belle interaction là-bas. De plus, je n'appellerais pas ce CPS,wrap
n'est pas utilisé comme une continuation, il n'est pas construit "à l'intérieur", il est empilé - par récursivité - à l'extérieur. Peut-être que s'il était utilisé comme continuation, vous pourriez vous débarrasser de cesid
s superflus ... btw nous pouvons voir ici encore une fois cet ancien modèle d'argument fonctionnel utilisé comme indicateur de ce qu'il faut faire, en basculant entre les deux modes d'action (Just
ouid
).Je pense que la version CPS à laquelle vous avez répondu vous-même est la meilleure, mais pour être complet, voici quelques idées supplémentaires. (EDIT: la réponse de Buhr est maintenant la plus performante.)
La première idée est de se débarrasser de l'
closestSoFar
accumulateur " " et de laisser leGT
cas gérer toute la logique de choisir la valeur la plus à droite la plus petite que l'argument. Sous cette forme, leGT
boîtier peut renvoyer directement unJust
:C'est plus simple, mais prend un peu plus d'espace sur la pile lorsque vous rencontrez de nombreux
GT
cas. Techniquement, vous pouvez même l'utiliserfromMaybe
sous la forme d'un accumulateur (c'est-à-dire remplacer l'fromJust
implicite dans la réponse de luqui), mais ce serait une branche redondante et inaccessible.L'autre idée qu'il y a vraiment deux "phases" de l'algorithme, une avant et une après avoir frappé un
GT
, donc vous le paramétrez par un booléen pour représenter ces deux phases, et utilisez des types dépendants pour coder l'invariant qu'il y aura toujours un aboutir à la deuxième phase.la source
Que diriez-vous
?
la source
Just
mais est total. Je sais que votre solution telle qu'elle est écrite est en fait totale, mais elle est fragile dans la mesure où une modification apparemment sûre pourrait alors entraîner un creux.Just
, il ajoutera donc un test pour s'assurer que ce n'est pas àNothing
chaque fois qu'il se répète.Non seulement nous savons toujours
Just
, après sa première découverte, nous savons aussi toujoursNothing
jusque- là. C'est en fait deux "logiques" différentes.Donc, nous allons d'abord à gauche, alors expliquons cela :
Le prix est que nous répétons au plus une étape au plus une fois.
la source