callCC
дает вам семантику «раннего возврата», но в монадическом контексте.
Скажем, вы хотите doOne
, и если это возвращает True
, вы немедленно останавливаетесь, иначе вы продолжаетедо doTwo
и doThree
:
doOne :: Cont r Bool
doTwo :: Cont r ()
doThree :: Cont r ()
doThings :: Cont r ()
doThings = do
one <- doOne
if one
then pure ()
else do
doTwo
doThree
Видите, что if
разветвляется там? Одна ветвь не так уж и плоха, с ней можно иметь дело, но представьте, что есть несколько таких точек, где вы просто хотите получить залог? Это очень уродливо и очень быстро.
С callCC
вы можете получить «ранний возврат»: вы делаете залог в точке ветвления и не должны вкладывать остальные вычисления:
doThings = callCC \ret -> do
one <- doOne
when one $ ret ()
doTwo
doThree
Гораздо приятнее читать!
Что еще более важно, поскольку ret
здесь не является специальным синтаксисом (например, return
в C-подобных языках), а просто значением, аналогичным любому другому, вы можетепередать его и другим функциям! И эти функции могут затем выполнять то, что называется «нелокальным возвратом» - то есть они могут «останавливать» вычисления doThings
, даже от нескольких вложенных глубоких вызовов. Например, я мог бы выделить проверку результата doOne
в отдельную функцию checkOne
, например:
checkOne ret = do
one <- doOne
when one $ ret ()
doThings = callCC \ret -> do
checkOne ret
doTwo
doThree