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

#haskell #monads #unsafe-perform-io

#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. Может кто-нибудь подсказать мне правильный способ выполнить то, что я пытаюсь сделать?

Комментарии:

1. Почему вы вычитаете 1 из n , прежде чем брать его квадратный корень? Это будет работать только для чисел, которые не являются квадратами простых чисел, и потерпит неудачу, например, для 25.

2. @jwodder, спасибо за правильный алгоритм простых чисел.

Ответ №1:

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


tl; dr; использовать 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 может выполняться один или два раза или любое другое количество раз.

Ответ №2:

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

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

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

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

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

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

Но если мы попытаемся это реализовать, это не сработает, поскольку мы не можем избежать монады ввода-вывода, поскольку тип putStrLn равен putStrLn :: String -> IO () . Для того, чтобы объединить его с вычислением до a 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 ? Быстрый поиск в b» rel=»nofollow»>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 снизу.

Комментарии:

1. Пожалуйста, обратите внимание, что вы никогда не должны использовать эту функцию unsafePerformIO . Почти всегда есть лучшее решение. Эта функция — зло; она позволяет вам делать вещи, которые иначе невозможны, и разрушать некоторые предположения о безопасности, которые компилятор мог бы сделать в противном случае. Если вы используете unsafePerformIO , подумайте дважды, и если вы не уверены на 100%, не используйте его ! Есть несколько допустимых вариантов использования, например trace , но они редки.

2.@FUZxxl: Я хорошо осведомлен о достоинствах и опасностях unsafePerformIO . Но для реализации того, о чем просил espertus, я не вижу другого варианта. Как вы указали, trace на самом деле реализовано в терминах unsafePerformIO . Я бы сказал, что он относится к той же категории кода, который обычно следует обрабатывать с помощью длинной палки.

3. И вы бы также сказали, что вы никогда не должны использовать trace , кроме как для отладки? Например, вы никогда не должны использовать его для печати информации, которую пользователи должны читать, когда код находится в производстве?

4. @MatrixFrog: точно. trace следует использовать только для быстрой и грязной отладки. (с акцентом на грязный ). В ситуациях, когда вы имеете дело с видимым пользователем выводом, вам, скорее всего, уже нужно запустить некоторый код в монаде ввода-вывода. trace Функция, например unsafePerformIO , не является ссылочно прозрачной. Возможно, вы не всегда видите свои пользовательские сообщения так, как вы их ожидаете.

5. @oliver Могу ли я добавить небольшое замечание о том, что никогда не следует использовать функцию, которая не должна быть названа?