Рассмотрим следующий пример.
newtype TooBig = TooBig Int deriving Show
choose :: MonadPlus m => [a] -> m a
choose = msum . map return
ex1 :: (MonadPlus m, MonadError TooBig m) => m Int
ex1 = do
x <- choose [5,7,1]
if x > 5
then throwError (TooBig x)
else return x
ex2 :: (MonadPlus m, MonadError TooBig m) => m Int
ex2 = ex1 `catchError` handler
where
handler (TooBig x) = if x > 7
then throwError (TooBig x)
else return x
ex3 :: Either TooBig [Int]
ex3 = runIdentity . runExceptT . runListT $ ex2
Каким должно быть значение ex3
? Если мы используем MTL, то ответ будет Right [7]
, что имеет смысл, потому что ex1
завершается, поскольку выдает ошибку, а handler
просто возвращает чистое значение return 7
, которое равно Right [7]
.
Однако в статье Олега Киселева и др. . авторы говорят, что это «неожиданный и нежелательный результат». Они ожидали, что результатом будет Right [5,7,1]
, потому что handler
восстанавливается после исключения, не вызывая его повторно. По сути, они ожидали, что catchError
будет перемещено в ex1
следующим образом.
newtype TooBig = TooBig Int deriving Show
choose :: MonadPlus m => [a] -> m a
choose = msum . map return
ex1 :: (MonadPlus m, MonadError TooBig m) => m Int
ex1 = do
x <- choose [5,7,1]
if x > 5
then throwError (TooBig x) `catchError` handler
else return x
where
handler (TooBig x) = if x > 7
then throwError (TooBig x)
else return x
ex3 :: Either TooBig [Int]
ex3 = runIdentity . runExceptT . runListT $ ex1
Именно это и делают расширяемые эффекты. Они изменяют семантику программы, перемещая обработчики эффектов ближе к источнику эффекта. Например, local
перемещается ближе к ask
, а catchError
перемещается ближе к throwError
. Авторы статьи рекламируют это как одно из преимуществ расширяемых эффектов перед монадными преобразователями, утверждая, что монадные преобразователи обладают «негибкой семантикой».
Но что, если я по какой-то причине хочу, чтобы результат был Right [7]
вместо Right [5,7,1]
? Как показано в приведенных выше примерах, преобразователи монад могут использоваться для получения обоих результатов. Однако, поскольку расширяемые эффекты всегда перемещают обработчики эффектов ближе к источнику эффекта, кажется невозможным получить результат Right [7]
.
Итак, вопрос в том, как с помощью расширяемых эффектов получить «негибкую семантику монадных преобразователей»? Можно ли предотвратить приближение отдельных обработчиков эффектов к источнику эффекта при использовании расширяемых эффектов? Если нет, то является ли это ограничением расширяемых эффектов, которое необходимо устранить?