Подтвердить что ты не робот

Haskell: понимание сообщений об ошибках "Нет экземпляра для" в ghci

Вопрос 1

Привет, если в WinGHCi я намеренно делаю следующий неправильный фрагмент кода:

3 4

Тогда сообщение об ошибке, которое я получаю,

<interactive>:1:1:
    No instance for (Num (a0 -> t0))
      arising from the literal `3'
    Possible fix: add an instance declaration for (Num (a0 -> t0))
    In the expression: 3
    In the expression: 3 4
    In an equation for `it': it = 3 4

Что означает No instance for (Num (a0 -> t0))?

Вопрос 2

Почему следующий фрагмент кода:

(+) 2 3 4
<interactive>:1:7:
    No instance for (Num (a0 -> t0))
      arising from the literal `3'
    Possible fix: add an instance declaration for (Num (a0 -> t0))
    In the second argument of `(+)', namely `3'
    In the expression: (+) 2 3 4
    In an equation for `it': it = (+) 2 3 4

вывести несколько другую ошибку из второго фрагмента кода:

2+3 4
<interactive>:1:3:
    No instance for (Num (a1 -> a0))
      arising from the literal `3'
    Possible fix: add an instance declaration for (Num (a1 -> a0))
    In the expression: 3
    In the second argument of `(+)', namely `3 4'
    In the expression: 2 + 3 4

А именно, в первом фрагменте кода No instance for (Num (a0 -> t0)), где, как и во втором фрагменте кода, No instance for (Num (a1 -> a0)).


[Ответ на ehird]

(вопросы переместились из комментариев ответа):

1) Я понимаю, что последние два выражения отличаются друг от друга, но вы говорите, что я не должен пытаться понять, почему интерпретатор выбирает (Num (a0 -> t0)) для первого и (Num(a1 -> a0)) для последнего, кроме того, что они разные

2) Привет, а с первым, когда вы говорите "Но нет экземпляра Num для функций", что вы имеете в виду? Извините, я не понимаю, что такое понятие экземпляра. Кроме того, просто из любопытства вы могли бы использовать свой метод экземпляра Num (a -> b), чтобы каким-то образом интерпретировать интерпретатор 3 4 как 4 modulo 3?

4b9b3361

Ответ 1

Мое намерение состоит в том, чтобы дополнить ehird ответ немного более подробным объяснением. Когда вы написали выражение

3 4

Затем интерпретатор Haskell считает, что вы пытаетесь применить функцию 3 к любому 4. Для того, чтобы Haskell интерпретировал 3 как функцию, ему необходимо сделать вызов функции

fromInteger :: Integer -> (a -> b)

чтобы получить функцию (т.е. что-то типа a -> b) из целого числа 3. Теперь fromInteger определяется в Num typeclass, чтобы иметь подпись

instance Num x where
    fromInteger :: Integer -> x

то есть. когда вы делаете тип x экземпляром класса Num, вы предоставляете реализацию fromInteger, которая сообщает Haskell, как преобразовать целочисленный литерал в x. В вашем случае x - это тип функции a -> b. Так что сделай это!


Во-первых, некоторые шаблоны. Чтобы сделать x экземпляр Num, Haskell требует, чтобы мы также сделали его экземпляром Show и Eq:

instance Show (a -> b) where show f = "<function>"
instance Eq (a -> b) where f == g = False

Теперь скажем, что мы хотим интерпретировать 3 4 как "4 по модулю 3". Затем нам нужно сообщить Haskell, как интерпретировать любое целое число как функцию, которая вызывает mod. Более того, поскольку mod принимает только интегральные типы (имеет подпись mod :: Integral a => a -> a -> a), то нам нужно также ограничить типы a и b:

instance (Integral a, Integral b) => Num (a -> b) where

Чтобы создать экземпляр Num, нам нужно предоставить реализации (+), (-), (*) и fromIntegral (на самом деле мы должны также определить пару других функций, но не будем беспокоиться об этом сейчас).

Существует довольно естественный способ определения сложения, вычитания и умножения (весь код здесь является частью экземпляра Num и должен иметь отступ по отношению к объявлению экземпляра)

    f + g = \x -> f x + g x
    f - g = \x -> f x - g x
    f * g = \x -> f x * g x

то есть. когда вы добавляете две функции f и g, вы получаете новую функцию, которая применяет как f, так и g к ее аргументу, а затем добавляет их вместе. Поскольку нам потребовалось, чтобы результат применения f и g был интегрального типа, мы знаем, что имеет смысл добавить свои выходы.

Чтобы интерпретировать целое число как функцию, мы можем написать

    fromInteger n = \m -> fromIntegral m `mod` fromIntegral n

то есть. когда у нас есть целое число n, мы возвращаем функцию параметра m, которая при вызове гарантирует, что оба аргумента одного типа (путем вызова fromIntegral на обоих из них), а затем использует их как аргументы к функции mod.

Наконец, немного больше шаблонов, чтобы остановить Haskell, жалуясь:

    abs f = undefined
    signum f = undefined

Мы можем проверить это. У меня есть код в файле numfun.hs. Я загружаю интерпретатор Haskell и загружаю файл:

Prelude> :l numfun.hs
[1 of 1] Compiling Main             ( numfun.hs, interpreted )
Ok, modules loaded: Main.

Теперь я могу определить некоторые функции:

*Main> let f = (+ 1)
*Main> let g = (* 2)

Я могу добавить их или вычесть из них:

*Main> (f + g) 3   -- (3+1) + (3*2)
10
*Main> (f - g) 3   -- (3+1) - (3*2)
-2

И я могу вызывать числа как функции:

*Main> 3 4         -- 4 `mod` 3
1

Ответ 2

Первая ошибка возникает из-за того, что целочисленный литерал типа 4 может быть любого типа с экземпляром Num. То есть 4 имеет тип (Num a) => a, поэтому он может служить как Integer, a Double, a Rational и т.д. Так как вы применили 3 к аргументу (4), он знает, что в контексте 3 должен быть тип функции (т.е. a0 -> t0 для некоторых a0 и t0). Но нет экземпляра Num для функций, поэтому использование 3 как функции недопустимо. Если вы добавили instance Num (a -> b), это сработает, но вы, вероятно, не захотите.

Что касается последнего, два сообщения об ошибках эквивалентны; имена, созданные GHC, не имеют особого значения. Буквы обычно выводятся из переменных типа в типах функций, которые вы используете, и числа добавляются, чтобы держать вещи недвусмысленными. В этом случае второе выражение эквивалентно (+) 2 (3 4) (потому что приложение функции связывается более жестко, чем любой инфиксный оператор), что не совсем так же, как ваш первый фрагмент кода.