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