(Inspirado pela minha resposta a esta pergunta .)
Considere este código (ele deve encontrar o maior elemento que é menor ou igual a uma determinada entrada):
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
Isso não é muito preguiçoso. Depois que o GT
caso é inserido, sabemos com certeza que o valor final de retorno será Just
algo em vez de Nothing
, mas o Just
ainda não estará disponível até o final. Eu gostaria de tornar isso mais preguiçoso para que ele Just
fique disponível assim que o GT
caso for inserido. Meu caso de teste para isso é que eu quero Data.Maybe.isJust $ closestLess 5 (Node 3 () Leaf undefined)
avaliar em True
vez de chegar ao fundo. Aqui está uma maneira de pensar em fazer isso:
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
No entanto, agora estou me repetindo: a lógica principal está agora em ambos closestLess
e em precise
. Como posso escrever isso para que seja preguiçoso, mas sem me repetir?
fonte
A partir de minha implementação não preguiçosa, primeiro refatorei
precise
para receberJust
como argumento e generalizei seu tipo de acordo:Então, eu mudei para fazer
wrap
cedo e chame-se deid
noGT
caso:Isso ainda funciona exatamente como antes, exceto pelo benefício da preguiça adicionada.
fonte
id
s no meioJust
e a final são(k,v)
eliminados pelo compilador? provavelmente não, as funções devem ser opacas, e você poderia (usualmente digitar) usado emfirst (1+)
vez deid
tudo o que o compilador sabe. mas isso cria um código compacto ... é claro, meu código é o desenrolar e a especificação de vocês aqui, com a simplificação adicional (a eliminação dosid
). Também é muito interessante como o tipo mais geral serve como restrição, uma relação entre os valores envolvidos (embora não seja suficientemente rígido,first (1+)
sendo permitido comowrap
).precise
é usado em dois tipos, correspondendo diretamente às duas funções especializadas usadas na variante mais detalhada. boa interação lá. Além disso, eu não chamaria isso de CPS,wrap
não é usado como uma continuação, não é construído "por dentro", é empilhado - por recursão - por fora. Talvez, se fosse usado como continuação, você poderia se livrar daquelas coisas estranhasid
... mas podemos ver aqui mais uma vez aquele velho padrão de argumento funcional usado como indicador do que fazer, alternando entre os dois cursos de ação (Just
ouid
).Eu acho que a versão do CPS que você respondeu consigo mesma é a melhor, mas para completar, aqui estão mais algumas idéias. (EDIT: A resposta de Buhr agora é a que tem maior desempenho.)
A primeira idéia é se livrar do "
closestSoFar
" acumulador e, em vez disso, deixar oGT
caso lidar com toda a lógica de escolher o valor mais à direita menor que o argumento. Nesse formulário, oGT
caso pode retornar diretamente umJust
:Isso é mais simples, mas ocupa um pouco mais de espaço na pilha quando você atinge muitos
GT
casos. Tecnicamente, você pode até usá-lofromMaybe
na forma de acumulador (ou seja, substituindo ofromJust
implícito na resposta de luqui), mas isso seria um ramo redundante e inacessível.A outra idéia de que realmente existem duas "fases" do algoritmo, uma antes e uma depois que você pressiona a
GT
, então você o define por um booleano para representar essas duas fases e usa tipos dependentes para codificar o invariante de que sempre haverá um resultar na segunda fase.fonte
E se
?
fonte
Just
mas é total. Sei que a sua solução, como está escrita, é de fato total, mas é frágil, pois uma modificação aparentemente segura pode resultar em um fundo.Just
, então adicionará um teste para garantir que não sejaNothing
sempre que ele voltar.Não apenas sabemos sempre
Just
, depois de sua primeira descoberta, também sempre sabemosNothing
até então. Na verdade, são duas "lógicas" diferentes.Então, vamos para a esquerda antes de tudo, para tornar isso explícito:
O preço é que repetimos no máximo uma etapa no máximo uma vez.
fonte