haskell: ошибка при попытке вызвать putStrLn в функции - PullRequest
5 голосов
/ 26 марта 2009

Я пытаюсь поместить вызов функции 'print out' в функцию haskell.

(простое отладочное сообщение).

Ниже приведен мой код и сообщение об ошибке от компилятора (ghc 6.10).

Я не совсем понимаю, почему это смешивает вызов puttr и пустой массив.

Пустой массив - это возвращаемое значение для этого конкретного случая (пока сообщение о выводе на самом деле просто заглушка).

Есть идеи, почему это не работает?

Спасибо

Мой код:

isAFactor :: Integer -> Integer -> Bool 
isAFactor x y = x `mod` y == 0

findFactors :: Integer -> Integer -> [Integer]
findFactors counter num = 
    let quotient = div num 2
    in
        if(counter >  quotient)
            then do
                putStrLn ("factorList is  : " ++ show quotient)  (*** Line 10***)
                []
        else if(isAFactor num counter)
            then [counter] ++ [quotient] ++ findFactors (counter + 1) num
        else
            findFactors (counter + 1) num

Ошибка от GHC

    test.hs:10:4:
    Couldn't match expected type `[a] -> [Integer]'
           against inferred type `IO ()'
    In the expression:
        putStrLn ("factorList is  : " ++ show quotient) []
    In the expression:
        do putStrLn ("factorList is  : " ++ show quotient) []
    In the expression:
        if (counter > quotient) then
            do putStrLn ("factorList is  : " ++ show quotient) []
        else
            if (isAFactor num counter) then
                  [counter] ++ [quotient] ++ findFactors (counter + 1) num
            else
                findFactors (counter + 1) num

Ответы [ 4 ]

20 голосов
/ 26 марта 2009

Важно помнить, что Haskell - это pure функциональный язык. Это означает, что функции не могут иметь никаких побочных эффектов, включая печать сообщений отладки на экран.

Однако можно нарушить эту чистоту, и это может быть полезно при отладке. Взгляните на модуль Debug.Trace . Там вы найдете функцию trace :: String -> a -> a. Вы можете использовать его в своем коде так:

import Debug.Trace

isAFactor :: Integer -> Integer -> Bool 
isAFactor x y = x `mod` y == 0

findFactors :: Integer -> Integer -> [Integer]
findFactors counter num = 
    let quotient = div num 2
    in
        if(counter >  quotient)
                then trace ("factorList is: " ++ show quotient) [] 
        else if(isAFactor num counter)
            then [counter] ++ [quotient] ++ findFactors (counter + 1) num
        else
            findFactors (counter + 1) num

Как следует из комментариев:

Хаскель также является ленивым языком. Выражение не оценивается до того, как результат действительно необходим. Использование функции трассировки может быть немного запутанным в ленивой настройке, поскольку не всегда легко понять, когда сообщение трассировки выводится на экран (если оно вообще печатается).

Поскольку язык haskell - это совсем другой язык, возможно, лучше всего попытаться разработать программы таким же образом. Попробуйте рассуждать о своих функциях вместо использования trace и подобных «не чистых» конструкций. Научитесь пользоваться преимуществами мощной системы типов haskells и используйте (например) QuickCheck для проверки своей функции после прохождения проверки типов.

9 голосов
/ 26 марта 2009

Пост Джонаса довольно хорошо освещает ваш вопрос, поэтому я дам вам идиоматическое переписывание вашей функции findFactors. Я нашел это полезным для меня, когда впервые учился.

Таким образом, вы хотите найти все факторы данного числа n, просматривая каждое число от 1 до n/2, проверяя, является ли это коэффициент n, и составляя список тех, которые есть.

Ваша версия (с минимальными изменениями, чтобы заставить ее работать):

findFactors :: Integer -> Integer -> [Integer]
findFactors counter num = 
    let quotient = div num 2
    in
        if(counter >  quotient)
            then []
        else if(isAFactor num counter)
            then [counter] ++ findFactors (counter + 1) num
        else
            findFactors (counter + 1) num

Несколько изменений форматирования, чтобы сделать его более читабельным:

findFactors :: Integer -> Integer -> [Integer]
findFactors counter num
  | counter > div num 2 = []
  | otherwise = if num `isAFactor` counter 
                then counter:findFactors (counter+1) num
                else findFactors (counter + 1) num

Это хорошо, но в нескольких отношениях это далеко не идеально. Во-первых, он пересчитывает частное каждый раз, когда вызывается findFactors, то есть n/2 делений (хотя ghc -O2, кажется, осознает это и вычисляет его только один раз). Во-вторых, досадно иметь дело с этой переменной счетчика везде. В-третьих, это все еще довольно важно.

Еще один способ взглянуть на проблему - взять список целых чисел от 1 до n/2 и отфильтровать только те, которые являются коэффициентами n. Это переводит довольно прямо на Haskell:

findFactors :: Integer -> [Integer]
findFactors num = filter (isAFactor num) [1..(num `div` 2)]

Может быть неожиданностью обнаружить, что он имеет те же характеристики производительности, что и вышеприведенная версия. Haskell не нужно выделять память для всего списка одновременно до n/2, он может просто генерировать каждое значение по мере необходимости.

8 голосов
/ 27 марта 2009

Другие хорошо объяснили ваш код, поэтому позвольте мне помочь вам расшифровать сообщение об ошибке.

Couldn't match expected type `[a] -> [Integer]'
       against inferred type `IO ()'
In the expression:
    putStrLn ("factorList is  : " ++ show quotient) []

Я пропустил все остальные части "В выражении"; они просто показывают все больше и больше окружающего контекста.

Все в Haskell является выражением, поэтому у всего есть тип. Это включает в себя что-то вроде "putStrLn". Если вы наберете «: t putStrLn» в GHCi, вы увидите ответ:

putStrLn :: String -> IO ()

Это означает, что putStrLn - это функция, которая принимает строку и возвращает «действие ввода-вывода», которое в данном случае является действием вывода сообщения на экран. В вашем коде вы дали строку «putStrLn», поэтому компилятор сделал вывод, что выражение «putStrLn ( stuff )» имеет тип «IO ()». Это часть «выведенного типа» в сообщении об ошибке компилятора.

Тем временем компилятор также делал вывод типов в другом направлении, извне. Среди прочего он заметил, что выражение this "putStrLn ( stuff )", казалось, применялось к пустому списку. , который имеет тип «[a]» (то есть список чего-то, мы не знаем, что). Кроме того, результат всего выражения должен иметь тип «[Integer]». Следовательно, выражение «putStrLn ( stuff )» должно быть функцией, превращающей «[]» в список целых чисел, тип которого записывается как «[a] -> [Integer]». Это часть ожидаемого типа сообщения об ошибке.

В этот момент компилятор пришел к выводу, что он не может сопоставить эти два типа, поэтому он сообщил об ошибке.

"Не удалось сопоставить ожидаемый тип ' Foo ' с логическим типом ' Bar '", вероятно, является наиболее распространенным сообщением об ошибке, которое вы получаете при попытке компилировать Haskell, поэтому его стоит попробовать читать и понимать это. Посмотрите на предполагаемый тип и попытайтесь выяснить, какая часть выражения в кавычках имеет этот тип. Затем попытайтесь выяснить, почему компилятор ожидал чего-то другого, посмотрев на окружающий код.

3 голосов
/ 26 марта 2009

Обновлено с уточнениями

Проблема в том, что IO в Haskell является монадическим, блок, начинающийся с do, является синтаксическим сахаром для объединения монадических выражений (иногда называемых операторами) с монадическими операторами. В данном случае рассматриваемая монада - это монада ввода / вывода, что может быть выведено из вызова putStrLn. [] во второй строке блока do на самом деле , а не значение всего блока do, скорее, оно интерпретируется как последний аргумент putStrLn; не то чтобы он принимает второй аргумент, но компилятор даже не доходит до того, чтобы выяснить это, потому что он завершается раньше с ошибкой типа, которую вы процитировали. Чтобы сделать эту строку командой, вы должны поставить перед ней, например, return, другую монадическую функцию (т.е. return []). Я не говорю, однако, что это поможет вам решить вашу проблему.

Ошибка типа связана с тем, что монадические выражения IO всегда имеют тип IO _; в вашем случае блок do также имеет этот тип, который явно несовместим с [Integer], типом, который вы указали в подписи.

В общем, потому что Haskell - это чисто функциональный язык с монадическим IO, когда вы находитесь внутри монады IO, выхода из нее нет, это противоречиво. т. е. если функция имеет блок do с операциями ввода-вывода, ее сигнатура обязательно будет содержать тип IO _, как и сигнатура всех других функций, вызывающих эту функцию, и т. д. "функционирует, а монада IO - нет.)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...