Отладка нежелательной строгости?

У меня проблема, я не знаю, как рассуждать. Я собирался спросить, может ли кто-нибудь помочь мне с конкретной проблемой, но мне стало понятно, что я могу задать более общий вопрос и, надеюсь, получить лучшее общее понимание в результате. С надеждой. Итак, вот:

Это обычно достаточно очевидно, когда ваша программа слишком ленива, потому что вы в конечном итоге сталкиваетесь с такими яркими проблемами, как утечки пространства. У меня противоположная проблема: моя программа слишком строгая. Я пытаюсь связать узлы и найти, что некоторые вещи я пытаюсь сделать как-то победит лени, в которой я нуждаюсь. Итак, мой общий вопрос: как отлаживать нежелательную строгость?


Для полноты, здесь мой конкретный случай: я в RWS, где компонент записи заполняет карту, а компонент читателя наблюдает за конечным состоянием этой карты. Я не могу сделать ничего строгого с этой картой, прежде чем закончить ее заполнение. Кажется, нет никакой проблемы для поиска значений на карте, например:

do
  m <- ask
  val <- m ! key
  doSomething val -- etc.

Но (!) не удается использовать error, где вместо этого я предпочел бы потерпеть неудачу, используя мою монаду fail. Поэтому я хотел бы сделать что-то вроде следующего:

do
  m <- ask
  maybe
    (fail "oh noes")
    (doSomething)
    (lookup key m)

Это заставляет мою программу <<loop>>, которую я не понимаю. Мне кажется, что это не должно быть более строгим, чем использование (!), но, очевидно, я ошибаюсь...

Ответ 1

Ваш первый пример строгий на карте. Следующее просматривает print "1", затем запускает его, и программа на самом деле печатает 1. Конечно, это требует оценки m.

main = do let m = Map.fromList [(1, print "1")]
          val <- m ! 1
          return val

Вероятно, вы должны написать что-то, что только читает карту. Следующее не является строгим, поскольку val не используется в выражении case.

main = do let m = Map.fromList [(1, print "1")]
          let val = m ! 1
          return val

Второй пример строгий, потому что он проверяет, удалось ли выполнить результат lookup, чтобы решить, как завершить выполнение блока do. Это требует чтения карты. Это эквивалентно:

do m <- ask
   case lookup key m of
     Nothing -> fail "oh noes"
     Just x  -> doSomething x 

Проблемы строгости отладки

Оценка всегда принудительно выполняется выражением case или некоторыми встроенными операторами, такими как + для целых чисел. Если вы подозреваете, что ваша программа терпит неудачу, потому что значение принудительно до этого доступно, вам нужно будет узнать, какое значение принудительно и где оно принудительно.

Какое значение было принудительно?

В этом виде ошибок программа пытается оценить выражение, зависящее от результата собственной оценки. Вы можете использовать trace для отслеживания того, какое выражение оценивается. В этой проблеме, похоже, что значение m принудительно, поэтому используйте trace для печати сообщения непосредственно перед его вычислением:

do m1 <- ask
   let m = trace "Using m" m1
   ...

Если "Использовать m" - последний выход из вашей программы (до <<loop>>), вы приближаетесь к ошибке. Если он не выводится, то m не оценивается, поэтому проблема находится в другом месте. Если что-то следует за этой строкой на выходе, то программа продолжает выполнение, а ошибка возникает позже, поэтому проблема должна быть где-то еще.

Где это было принудительно?

Это говорит о том, что оценка прошла, по крайней мере, до того, как остановить. Но как далеко он прошел? Проблема на самом деле произошла намного позже? Чтобы убедиться в этом, попробуйте положить trace на то, что будет оцениваться позже. Мы знаем, что m оценивается для того, чтобы решить, какая ветвь maybe выполняется, поэтому мы можем положить trace в эти точки.

do m1 <- ask
   let m = trace "Using m" m1
   maybe (trace "Used m" $ fail "oh noes")
         (\x -> trace "Used m" $ doSomething x)
         (lookup key m)

Если вы видите "Использование m", а затем "Используется m" на выходе, вы знаете, что оценка m завершена, и программа продолжалась. Если вы видите только "Использование m", программа останавливается между этими точками. В этом конкретном случае вы не должны видеть "Used m", потому что maybe заставляет evaulation m и вызывает <<loop>>.