(Inspirado por mi respuesta a esta pregunta ).
Considere este código (se supone que debe encontrar el elemento más grande que sea menor o igual que una entrada dada):
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
Esto no es muy vago. Una vez que GT
se ingresa el caso, sabemos con certeza que el valor de retorno final será Just
algo más que Nothing
, pero Just
aún no estará disponible hasta el final. Me gustaría hacerlo más perezoso para que Just
esté disponible tan pronto como GT
se ingrese el caso. Mi caso de prueba para esto es que quiero Data.Maybe.isJust $ closestLess 5 (Node 3 () Leaf undefined)
evaluar en True
lugar de tocar fondo. Aquí hay una forma en que puedo pensar en hacer esto:
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
Sin embargo, ahora me estoy repitiendo: la lógica central ahora está en ambos closestLess
y en precise
. ¿Cómo puedo escribir esto para que sea flojo pero sin repetirme?
fuente
A partir de mi implementación no perezosa, primero refactoré
precise
para recibirJust
como argumento, y generalicé su tipo en consecuencia:Luego, lo cambié para hacerlo
wrap
temprano y llamarloid
en elGT
caso:Esto todavía funciona exactamente como antes, excepto por el beneficio de la pereza adicional.
fuente
id
s en el medioJust
y la final son(k,v)
eliminados por el compilador? Probablemente no, se supone que las funciones son opacas, y podría haberlo utilizado (de forma factible) enfirst (1+)
lugar deid
todo lo que el compilador sabe. pero es un código compacto ... por supuesto, mi código es el de desentrañar y especificar el suyo aquí, con la simplificación adicional (la eliminación de losid
s). También es muy interesante cómo el tipo más general sirve como una restricción, una relación entre los valores involucrados (aunque no lo suficientemente ajustados, con elfirst (1+)
permiso comowrap
).precise
se usa en dos tipos, que corresponden directamente a las dos funciones especializadas utilizadas en la variante más detallada. Buena interacción allí. Además, no llamaría a esto CPS,wrap
no se usa como una continuación, no está construido "en el interior", está apilado, por recursividad, en el exterior. Tal vez si se utiliza como continuación usted podría deshacerse de esas extrañasid
s ... por cierto podemos ver aquí una vez más que viejo patrón del argumento funcional utilizado como indicador de qué hacer, el cambio entre las dos líneas de acción (Just
oid
).Creo que la versión de CPS que respondió usted mismo es la mejor, pero para completar, aquí hay algunas ideas más. (EDITAR: la respuesta de Buhr es ahora la más eficaz).
La primera idea es deshacerse del "
closestSoFar
" acumulador y, en su lugar, dejar que elGT
caso maneje toda la lógica de elegir el valor de la derecha más pequeño que el argumento. De esta forma, elGT
caso puede devolver directamente unJust
:Esto es más simple, pero ocupa un poco más de espacio en la pila cuando llegas a muchos
GT
casos. Técnicamente, incluso podría usar esofromMaybe
en forma de acumulador (es decir, reemplazar lofromJust
implícito en la respuesta de luqui), pero eso sería una rama redundante e inalcanzable.La otra idea es que en realidad hay dos "fases" del algoritmo, una antes y otra después de presionar a
GT
, por lo que lo parametrizas con un booleano para representar estas dos fases, y usas tipos dependientes para codificar el invariante de que siempre habrá un resultado en la segunda fase.fuente
Qué tal si
?
fuente
Just
pero es total. Sé que su solución tal como está escrita es de hecho total, pero es frágil ya que una modificación aparentemente segura podría resultar en un fondo.Just
, por lo que agregará una prueba para asegurarse de que noNothing
se repita cada vez.No solo sabemos siempre
Just
, después de su primer descubrimiento, también siempre sabemosNothing
hasta entonces. Eso es en realidad dos "lógicas" diferentes.Entonces, primero vamos a la izquierda, así que explícalo:
El precio es que repetimos como máximo un paso como máximo una vez.
fuente