Почему `succ i` допустимо там, где` i :: Num a => a` (а не `Enum a`)? - PullRequest
3 голосов
/ 23 марта 2019

Похоже, это относится как к GHCi, так и к GHC.Сначала я покажу пример с GHCi.

Данный тип i выведен следующим образом:

Prelude> i = 1
Prelude> :t i
i :: Num p => p

Учитывая, что succ является функцией, определенной в Enum:

Prelude> :i Enum
class Enum a where
  succ :: a -> a
  pred :: a -> a
  -- …OMITTED…

и что Num не является «подклассом» (если я могу использовать этот термин) Enum:

class Num a where
  (+) :: a -> a -> a
  (-) :: a -> a -> a
-- …OMITTED…

, почему succ i не возвращаетошибка?

Prelude> succ i
2 -- works, no error

Я бы ожидал, что :type i будет выведено примерно так:

Prelude> i = 1
Prelude> :type i
i :: (Enum p, Num p) => p

(я использую 'GHC v. 8.6.3')

ДОПОЛНЕНИЕ:

После прочтения комментария @RobinZigmond и ответа @AlexeyRomanov я заметил, что 1 можно интерпретировать как один из многих типов и один измного классов.Благодаря ответу @AlexeyRomanov я понимаю гораздо больше о правилах по умолчанию, используемых для определения того, какой тип использовать для неоднозначных выражений.

Однако я не думаю, что ответ Алексея отвечает именно на мой вопрос.Мой вопрос о типе i.Дело не в типе succ i.

Речь идет о несоответствии между succ типом аргумента (Enum a) и очевидным типом i (a Num a).

Я сейчас начинаю пониматьчто мой вопрос должен исходить из неверного предположения: «что если i будет i :: Num a => a, то i может быть ничем иным '.Поэтому я был озадачен, увидев, что succ i был оценен без ошибок.

GHC также, по-видимому, выводит Enum a в дополнение к тому, что было явно объявлено.

x :: Num a => a
x = 1
y = succ x -- works

Однако это не такдобавление Enum a, когда переменная типа появляется как функция:

my_succ :: Num a => a -> a
my_succ z = succ z -- fails compilation

Мне кажется, что ограничения типа, прикрепленные к функции, более строгие, чем ограничения, применяемые к переменной.

GHC говорит, что my_succ :: forall a. Num a => a -> a, а заданный forall a не появляется в сигнатуре типа ни i, ни x Я думал, что это означает, что GHC не собирается выводить больше классов для my_succ типов.

Но это опять-таки кажется неправильным: я проверил эту идею с помощью следующего (первый раз, когда я набираю RankNTypes), и, очевидно, GHC по-прежнему выводит Enum a:

{-# LANGUAGE RankNTypes #-}

x :: forall a. Num a => a
x = 1
y = succ x

Так что, похоже, правила выводадля функций более строгие, чем для переменных?

1 Ответ

8 голосов
/ 23 марта 2019

Да, тип succ i выведен так, как вы ожидаете:

Prelude> :t succ i
succ i :: (Enum a, Num a) => a

Этот тип неоднозначен, но он удовлетворяет условиям в правилах по умолчанию для GHCi:

Найти все нерешенные ограничения. Тогда:

  • Найдите те, которые имеют форму (C a), где a - переменная типа, и разбейте эти ограничения на группы, которые имеют общую переменную типа a.

В этом случае есть только одна группа: (Enum a, Num a).

  • Оставьте только те группы, в которых хотя бы один из классов является интерактивным (определено ниже).

Эта группа сохраняется, потому что Num - это интерактивный класс.

  • Теперь для каждой оставшейся группы G по очереди попробуйте каждый тип ty из списка типов по умолчанию; если установка a = ty позволит полностью разрешить ограничения в G. Если это так, по умолчанию от a до ty.

  • Тип блока () и тип списка [] добавляются в начало стандартного списка типов, которые используются при выполнении значений по умолчанию.

Список типов по умолчанию (sic) по умолчанию (с дополнениями из последнего предложения) default ((), [], Integer, Double).

Таким образом, когда вы делаете Prelude> succ i для фактической оценки этого выражения (примечание :t не оценивает полученное выражение), a устанавливается на Integer (первый из этого списка удовлетворяет ограничениям), и результат печатается как 2.

Вы можете увидеть, в чем причина, изменив значение по умолчанию:

Prelude> default (Double)
Prelude> succ 1
2.0

Для обновленного вопроса:

Теперь я начинаю осознавать, что мой вопрос должен исходить из неверного предположения: «если i выведено как i :: Num a => a, то i не может быть ничем иным». Поэтому я был озадачен, увидев, что succ i был оценен без ошибок.

i может быть ничем иначе (то есть ничем, что не соответствует этому типу), но его можно использовать с менее общими (более конкретными) типами: Integer, Int. Даже со многими из них в выражении сразу:

Prelude> (i :: Double) ^ (i :: Integer)
1.0

И это не влияет на сам тип i: он уже определен, а его тип фиксирован. Хорошо, пока?

Что ж, добавление ограничений также делает тип более конкретным, поэтому (Num a, Enum a) => a более специфично, чем (Num a) => a:

Prelude> i :: (Num a, Enum a) => a
1

Поскольку, конечно, любой тип a, который удовлетворяет обоим ограничениям в (Num a, Enum a), удовлетворяет только Num a.

Однако он не добавляет Enum a, когда переменная типа отображается как функция:

Это потому, что вы указали подпись, которая не позволяет этого. Если вы не ставите подпись, нет никаких оснований выводить ограничение Num. Но, например,

Prelude> f x = succ x + 1

выведет тип с обоими ограничениями:

Prelude> :t f
f :: (Num a, Enum a) => a -> a

Так что кажется, что правила вывода для функций более строгие, чем правила для переменных?

На самом деле все наоборот из-за ограничения мономорфизма (по умолчанию не в GHCi). Тебе действительно повезло, что ты не столкнулся с этим здесь, но ответ уже достаточно длинный. Поиск термина должен дать вам объяснения.

GHC говорит my_succ :: forall a. Num a => a -> a, а данный forall a не появляется в сигнатуре типа ни i, ни x.

Это красная сельдь. Я не уверен, почему это показано в одном случае, а не в другом, но у всех них есть это forall a за кадром:

Сигнатуры типа Haskell неявно определяются количественно. Когда используется языковая опция ExplicitForAll, ключевое слово forall позволяет нам точно сказать, что это значит. Например:

g :: b -> b

означает это:

g :: forall b. (b -> b)

(Кроме того, вам нужно ExplicitForAll, а не RankNTypes, чтобы записать forall a. Num a => a.)

...