в haskell, зачем мне указывать ограничения типа, почему компилятор не может их выяснить - PullRequest
6 голосов
/ 04 января 2011

Рассмотрим функцию,

add a b = a + b

Это работает:

*Main> add 1 2
3

Однако, если я добавлю сигнатуру типа, указывающую, что я хочу добавить вещи того же типа:

add :: a -> a -> a
add a b = a + b

Я получаю сообщение об ошибке:

test.hs:3:10:
    Could not deduce (Num a) from the context ()
      arising from a use of `+' at test.hs:3:10-14
    Possible fix:
      add (Num a) to the context of the type signature for `add'
    In the expression: a + b
    In the definition of `add': add a b = a + b

Итак, GHC ясно может сделать вывод, что мне нужно ограничение типа Num, так как оно только что сообщило мне:

add :: Num a => a -> a -> a
add a b = a + b

Работает.

Почему GHC требует, чтобы я добавил ограничение типа?Если я занимаюсь общим программированием, то почему оно не может работать для всего, кто знает, как использовать оператор +?

В программировании на C ++ вы можете сделать это легко:

#include <string>
#include <cstdio>

using namespace std;

template<typename T>
T add(T a, T b) { return a + b; }

int main()
{
    printf("%d, %f, %s\n",
           add(1, 2),
           add(1.0, 3.4),
           add(string("foo"), string("bar")).c_str());
    return 0;
}

Компилятор выясняет типы аргументов для add и генерирует версию функции для этого типа.Кажется, в подходе Хаскелла есть фундаментальное различие, можете ли вы описать его и обсудить компромиссы?Мне кажется, что это было бы решено, если бы GHC просто заполнил для меня ограничение типа, поскольку он, очевидно, решил, что это необходимо.Тем не менее, почему ограничение типа вообще?Почему бы просто не скомпилировать успешно, если функция используется только в допустимом контексте, где аргументы находятся в Num?

Ответы [ 4 ]

15 голосов
/ 04 января 2011

Весь смысл типов состоит в том, чтобы иметь формальный способ объявления правильного и неправильного способа использования функции.Тип (Num a) => a -> a -> a точно описывает, что требуется от аргументов.Если вы опустите ограничение класса, у вас будет более общая функция, которую можно использовать (ошибочно) в большем количестве мест.Куда бы ни шла функция, тип обязательно должен идти.Учтите это:

add :: a -> a -> a
add a b = a + b
foo :: [a -> a -> a]
foo = [add]
value :: [String]
value = [f "hello" "world" | f <- foo]

Вы хотите, чтобы компилятор отклонил это, верно?Как оно это делает?Добавляя ограничения классов и проверяя, чтобы они не были удалены, даже если вы не называете функцию напрямую.

Что отличается в версии C ++?Нет никаких ограничений класса.Компилятор заменяет int или std::string на T, затем пытается скомпилировать полученный код и ищет соответствующий оператор +, который он может использовать.Система шаблонов «более свободна», так как она принимает больше недопустимых программ, и это является признаком того, что она является отдельной стадией перед компиляцией.Я бы хотел бы изменить C ++, чтобы добавить семантику <? extends T> из дженериков Java.Просто изучите систему типов и узнайте, что параметрический полиморфизм «сильнее» шаблонов C ++, а именно он отклонит больше недопустимых программ.

15 голосов
/ 04 января 2011

Если вы не хотите указывать тип функции, просто не указывайте ее, и компилятор автоматически определит типы.Но если вы решите указать типы, они имеют , чтобы быть правильными и точными.

12 голосов
/ 04 января 2011

Я думаю, что вас может сбить с толку "сумасшедшая поэзия луны" сообщений об ошибках GHC. Это не значит, что it (будучи GHC) не может вывести ограничение (Num a). Это говорит о том, что ограничение (Num a) не может быть выведено из вашей подписи типа , которая, как он знает, должна присутствовать при использовании +. Следовательно, вы утверждаете, что эта функция имеет более общий тип, чем знает компилятор. Компилятор не хочет, чтобы вы лгали о своих функциях миру!

В первом приведенном вами примере без сигнатуры типа, если вы запустите :t add в ghci, вы увидите, что компилятор прекрасно знает, что ограничение (Num a) существует.

Что касается шаблонов C ++, помните, что они являются синтаксическими шаблонами и только полностью проверяются на тип в каждом экземпляре по мере их использования. Ваш шаблон add будет работать с любыми типами, если в каждом месте его использования есть подходящий оператор + и, возможно, преобразования, чтобы сделать экземпляр шаблона жизнеспособным. До этого момента никаких гарантий относительно шаблона быть не может ... поэтому тело шаблона должно быть "видимым" для каждого модуля, который его использует.

По сути, все, что может сделать C ++, - это проверить синтаксис шаблона, а затем сохранить его как своего рода очень гигиеничный макрос. Принимая во внимание, что Haskell генерирует реальную функцию для add (за исключением того, что он может также выбрать генерацию специфичных для типа специализаций для оптимизации).

3 голосов
/ 04 января 2011

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

f s = show $ read s

Компилятор говорит:

Ambiguous type variable `a' in the constraints:
Read a' arising from a use of `read' at src\Main.hs:20:13-18
`Show a' arising from a use of `show' at src\Main.hs:20:6-9
Probable fix: add a type signature that fixes these type variable(s)

(Как ни странно, кажется, что вы можете определить эту функцию в ghci, но, кажется, нет способа ее использовать)

Если вы хотите, чтобы что-то вроде f "1" работало, вам нужно , чтобы указать тип:

f s = show $ (read s :: Int) 
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...