Как использовать putStrLn для трассировки (Haskell)

Я пытаюсь заставить функцию Haskell показывать всякий раз, когда она применяется, добавляя вызов «putStrLn»:

isPrime2 1 = False

isPrime2 n = do
    putStrLn n
    null (filter (==0) (map (mod n) (filter isPrime2 [2..(floor (sqrt(fromIntegral (n-1))))])))

(Конечная цель — продемонстрировать, почему одна версия isPrime эффективнее другой.)

Когда я загружаю приведенный выше код в GHCi, я получаю сообщение об ошибке:

Не удалось сопоставить ожидаемый тип Bool с фактическим типом m0 b0

Я уверен, что это ошибка n00b. Может ли кто-нибудь сказать мне, как правильно выполнить то, что я пытаюсь сделать?


person Ellen Spertus    schedule 04.10.2011    source источник
comment
Почему вы вычитаете 1 из n перед тем, как извлечь из него квадратный корень? Это будет работать только для чисел, которые не являются квадратами простых чисел, и не будет работать, например, для 25.   -  person jwodder    schedule 04.10.2011
comment
@jwodder, спасибо за правильный алгоритм простых чисел.   -  person Ellen Spertus    schedule 05.10.2011


Ответы (2)


Проблема в том, что в Haskell существует строгое различие между чистыми функциями, такими как (+) и map, и нечистыми действиями, такими как putStrLn и main. Чистая функция должна всегда давать один и тот же результат при одинаковых входных данных и ничего не изменять. Это явно запрещает использование PutStr и друзей. Система типов фактически обеспечивает это разделение. Каждая функция, которая выполняет ввод-вывод или является нечистой, имеет IO перед своим типом.


тл;др; используйте trace из модуля Debug.Trace:

import Debug.Trace

isPrime2 1 = False
isPrime2 n = show n `trace` null (filter (==0) (map (mod n) (filter isPrime2 [2..(floor (sqrt(fromIntegral (n-1))))])))

Но имейте в виду, что результаты могут быть довольно неожиданными, поскольку нет никакой гарантии, что ваш код действительно запустится; аргумент trace может запускаться один или два раза или любое другое количество раз.

person fuz    schedule 04.10.2011

Всякий раз, когда у вас возникают такие ошибки типов, как Couldn't match expected type X with actual type Y, вы должны использовать систему типов haskell, которая поможет вам.
Итак, давайте посмотрим, в чем проблема:

У вас есть чистая функция с типом Int -> Bool. И вы хотите напечатать отладочный вывод, который явно не чистый (т.е. который находится в монаде IO).
Но в любом случае вы хотите написать s. й. по этим строкам:

foo x 
  | x > 0 = debug "ok" True
  | otherwise = debug "ohhh...no" False

Тем не менее, тип вашей функции должен быть foo :: Int -> Bool

Итак, давайте определим функцию debug, которая удовлетворит проверку типов. Он должен был бы принять String (ваше отладочное сообщение) и Bool (ваш результат) и оценить только Bool.

debug :: String -> Bool -> Bool
debug = undefined

Но если мы попытаемся реализовать это, это как бы не сработает, поскольку мы не можем избежать монады ввода-вывода, поскольку тип putStrLn — putStrLn :: String -> IO (). Чтобы совместить это с оценкой Bool, нам также придется поместить Bool в контекст IO:

debugIO msg result = putStrLn msg >> return result

Хорошо, давайте спросим у ghci тип этой функции:

Main> :t debugIO
debugIO :: String -> b -> IO b

Таким образом, мы получаем IO Bool, но нам понадобится только Bool.
Существует ли функция с типом IO b -> b? Быстрый поиск в hoogle дает нам подсказку:

Печально известный unsafePerformIO :: IO a -> a имеет нужный нам тип.
Итак, теперь мы могли реализовать нашу функцию debug в терминах debugIO:

debug :: String -> Bool -> Bool
debug s r = unsafePerformIO $ debugIO s r

что на самом деле в значительной степени то, что вы получаете с функцией trace в Debug.Trace, как уже было указано FUZxxl.
И поскольку мы согласны с тем, что никогда не следует использовать unsafePerformIO, использование функции trace предпочтительнее. Просто имейте в виду, что, несмотря на то, что это сигнатура чистого типа, на самом деле она также не ссылочно прозрачна и использует unsafePerformIO внизу.

person oliver    schedule 04.10.2011
comment
Обратите внимание, что вам никогда не следует использовать функцию unsafePerformIO. Практически всегда есть лучшее решение. Эта функция злая; это позволяет вам делать вещи, которые в противном случае были бы невозможны, и разрушить некоторые предположения безопасности, которые компилятор мог бы сделать в противном случае. Если вы используете unsafePerformIO, подумайте дважды, и если вы не уверены на 100%, не используйте его! Есть несколько законных вариантов использования, например trace, но они редки. - person fuz; 04.10.2011
comment
@FUZxxl: я хорошо осведомлен о достоинствах и опасностях unsafePerformIO. Но для того, чтобы реализовать то, о чем просил эспертус, я не вижу другого выхода. Как вы указали, trace является фактически реализованным с точки зрения unsafePerformIO. Я бы сказал, что он относится к той же категории кода, с которым обычно следует обращаться длинной палкой. - person oliver; 04.10.2011
comment
И не могли бы вы также сказать, что вы никогда не должны использовать trace, кроме как для отладки? Например, вы никогда не должны использовать его для печати информации, которую пользователи должны читать, когда код находится в производстве? - person MatrixFrog; 04.10.2011
comment
@MatrixFrog: точно. trace следует использовать только для быстрой и грязной отладки. (с акцентом на грязный). В ситуациях, когда вы имеете дело с видимым пользователем выводом, вам, скорее всего, уже приходится запускать некоторый код в IO Monad. Функция trace, как и unsafePerformIO, непрозрачна с точки зрения ссылок. Вы можете не всегда видеть свои пользовательские сообщения, как вы ожидаете. - person oliver; 04.10.2011
comment
Если вы просто введете тип вашей функции debug в Hoogle, вы получите trace в качестве первого обращения, так что вы могли бы остановиться на этом вместо того, чтобы вводить unsafePerformIO (функция, имя которой никогда не должно произноситься!) - person pat; 04.10.2011
comment
@oliver Могу ли я добавить небольшое замечание о том, что никогда не следует использовать функцию, которую нельзя называть? - person fuz; 04.10.2011
comment
@FUZxxl конечно. Программирование haskell, вы никогда не должны его использовать. В случае с авторами библиотек я на самом деле не совсем уверен... хотя есть случаи, когда это имеет смысл. Но я добавлю ваше замечание, чтобы сделать это более ясным. - person oliver; 05.10.2011