Haskell «Нет экземпляра для» ошибка типа в простом коде - PullRequest
2 голосов
/ 24 февраля 2012

Я действительно большой любитель Хаскелла.

У меня есть этот код:

4 sieve n i = if i < n
5             then
6                 do {
7                  innerSieve n;
8                  sieve n (i + 1);
9                 }
10             else -1
11 
12 innerSieve n = return n
13 
14 --innerSieve n i j = [x | x <- [i..n], x `mod` j == 0]

и у меня есть эта ошибка:

[1 of 1] Compiling Main             ( sieve.hs, interpreted )
Ok, modules loaded: Main.
*Main> sieve 10 2

<interactive>:1:1:
No instance for (Num (m0 b0))
  arising from a use of `sieve'
Possible fix: add an instance declaration for (Num (m0 b0))
In the expression: sieve 10 2
In an equation for `it': it = sieve 10 2
*Main> 

Я бился головой о стену, пытаясь понять, что значит «Нет экземпляра для (Num (m0 b0))». Что в мире такое m0 b0?

Я думал, что это может помочь:

*Main> :t sieve
sieve :: (Ord a, Num a, Num (m b), Monad m) => a -> a -> m b
*Main> 

EDIT: Я пытаюсь воссоздать сито эрастотенов, в основном делая рекурсивную функцию со списком. Я также хочу понять все в коде.

Ответы [ 4 ]

5 голосов
/ 24 февраля 2012

Ваш внутренний do блок имеет монадический тип. Это делает весь результат sieve монадическим типом (Monad m) => m b. Ветвь else вашего оператора if возвращает -1, поэтому мы знаем, что он также имеет числовой тип. Это делает результат вашей функции (Num (m b), Monad m) => m b. Это очень явно неправильно.

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

Кстати, вы можете найти выражение if лучше выраженное как

sieve n i
    | i < n     = -- contents of the then block
    | otherwise = -1
4 голосов
/ 24 февраля 2012

Haskell - это функциональный язык. Это особенно означает, что вы не говорите компилятору Do A, затем делаете B , а скорее вычисляете A - вы не указываете компилятору, в каком порядке делать вещи, скорее вы скажи то, что ты хочешь знать. Вы можете думать об этом как о наличии только одного (return) оператора. Таким образом, вам нужно переписать свой код, чтобы соответствовать этой парадигме.

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

Как уже показали другие, вы можете сделать что-то вроде этого. Канал (|) называется шаблон защиты и по сути работает как If-then-else:

sieve n i | i < n     = --result if condition is met
          | otherwise = -1

Помните, что вы не можете изменить значения переменных . Возможно, вам придется переписать ваш innerSieve, чтобы вернуть что-то.

3 голосов
/ 24 февраля 2012

Как уже говорили другие, вы почти наверняка не хотите использовать блок do в своем коде, где вы это делаете. do предназначен для Monads и его следует избегать при изучении Haskell, и, вероятно, он неуместен, поскольку единственное Monad здесь - [], которое игнорирует любое определение innerSieve. Код на Haskell описывает значения и типы в терминах других значений и типов. Программы на Haskell не говорят компьютеру, что делать, они говорят ему, что является . Это радикальный способ мышления, но очень мощный. do нотация - это специальный синтаксис для обработки монад, представляющих собой особый вид значений, которые можно использовать для чистого и эффективного кодирования вычислений (включая императивные вычисления и переходы состояний), но для хорошего использования требуется широкое понимание языка Haskell.

Мой совет новичкам на Haskell: «Избегайте монад и нотаций do до тех пор, пока вы не освоите: рекурсию, функции высшего порядка и классы типов. Вам не нужно do для тестирования ваших программ (используйте GHCi), поэтому изучите реальное функциональный образ мышления. Попробуйте программировать так, чтобы ваши программы ничего не делали! "

Итак, как бы вы пошли писать сито с Эратосфеном на Хаскеле? Есть много элегантных способов. Предупреждение: спойлеры впереди. Если вы хотите сделать это самостоятельно, перестаньте читать

Вы прокомментировали:

innerSieve n i j = [x | x <- [i..n], x `mod` j == 0]

Я не уверен, какой смысл n и i здесь. Было бы более элегантно написать

innerSieve j ls = [x | x <- ls, x `mod` j == 0]

который имеет тип

innerSieve :: Integral x => x -> [x] -> [x]

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

Вы могли бы использовать это, чтобы написать сито Эратосфена, но лучшим способом было бы получить все значения, которые не кратны. Причина в том, что тогда вы можете просто сложить их вместе, чтобы получить ваше полное простое сито

innerSieve j ls = [x | x <- ls, x `mod` j /= 0]

Здесь /= - это способ сказать Хаскеллу «не равный». Круто, так что давайте построим простое сито из этого

sieve (x:xs) = x : sieve (innerSieve x xs)
sieve [] = []

Что это говорит? ну, он берет список чисел и возвращает вам новый список, состоящий из первого из этих чисел, и сито применяется к остальным этим числам, за исключением тех, которые кратны первому. (x:xs) является шаблоном , который соответствует любому списку, кроме пустого списка, связывая x с заголовком списка и xs с хвостом списка. [] - это шаблон, который соответствует любому пустому списку.

Наконец, вы можете определить бесконечный список всех простых чисел

primes = sieve [2..]

Выражение [2..] создает список 2,3,4,5,6,7,etc, который продолжается вечно. Выглядит страшно (бесконечные списки), но ничего страшного из-за прославленной ленивой оценки Хаскелла. Чтобы получить n-е простое число, вы можете сказать:

nthPrime n = primes !! (n - 1)

, который просто индексируется в списке простых чисел. Haskell вычислит только минимум, необходимый для возврата результата, поэтому эта функция завершится, даже если primes бесконечно долго

или получить все простые числа до числа

primesUpToN n = take n primes

Итак, вся ваша кодовая база в конечном итоге будет:

sieve [] = []
sieve (x:xs) = x : sieve (innerSieve x xs) where
   innerSieve j ls = [x | x <- ls, x `mod` j /= 0]

primes = sieve [2..]

nthPrime n = primes !! (n - 1)

primesUpToN n = take n primes

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

0 голосов
/ 24 февраля 2012

Мало того, что это приводит к ошибке типа:

do {
   innerSieve n;
   sieve n (i + 1);
   }

Но оно имеет то же значение, что и

do {
   sieve n (i + 1);
   }

Вы вызвали функцию "innerSeive", но затем несделайте что-нибудь со значением.
Если вы хотите вычислить значение, а затем использовать значение в следующей операции, используйте оператор (>>=) (засахаривается как let x = innerseive n; f2 n; в блоке do).
Однако для этих математических функций держитесь подальше от do, let, (>>=) и т. Д.

...