Заблуждение о классах типов и назначении переменных в Haskell

Очень плохо знаком с Haskell и пытаюсь понять, как взаимодействуют классы типов и переменные.

Первое, с чем я поиграл, было:

i :: a; i = 1

Я ожидал, что, поскольку я был напечатан как можно более обобщенно, я мог присвоить ему абсолютно все, что угодно. (Я знаю, что, вероятно, ничего не могу сделать с переменной i, но это не важно.)

Но я был неправ. Вышеприведенное дает ошибку и требует, чтобы она была:

i :: Num a => a; i = 1

Поигравшись еще немного, я пришел к следующему:

g :: Num a => a -> a; g a = a + 1
g 1
(returned 2)
gg :: Num a => a; gg = g 1
gg
(returned 2)

Хорошо... пока все хорошо. Давайте попробуем дробный параметр.

g :: Num a => a -> a; g a = a + 1
g 1.3
(returned 2.3)
gg :: Num a => a; gg = g 1.3
(error)

Итак, пожалуйста... что такого в переменных, что вызывает это? С точки зрения нефункционального программирования это «выглядит», как будто у меня есть функция, которая возвращает значение с типом, реализующим Num, и пытается присвоить его переменной с типом, реализующим Num. Тем не менее, задание не выполняется.

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


person Marc L. Allen    schedule 01.05.2019    source источник
comment
Поскольку вы пишете g 1.3, это означает, что тип более ограничен, gg имеет тип gg :: Fractional a => a. Обратите внимание, однако, что (за исключением некоторых редких случаев) вы не должны вводить переменные, Haskell всегда будет использовать наиболее общий возможный тип. Вы не назначаете значения переменной, вы объявляете переменную: однажды присвоив значение, вы больше никогда не сможете изменить его значение.   -  person Willem Van Onsem    schedule 01.05.2019
comment
Связано: Как удобно работать с системой типов в Haskell?   -  person duplode    schedule 01.05.2019
comment
Я не понимаю. Извини. Первая часть :i Fractional — это класс Num a => Fractional a. Разве это не означает, что Fractional реализует класс типов Num? Или я путаюсь в функциях и переменных. Аргумент функции должен реализовывать определенные классы типов, но переменная может принимать только определенные типы? Как мне ввести переменную, чтобы получить результат от g? Только из определения типа g? Только что увидел ваше редактирование... Мне было интересно, было ли вводить переменные плохим ходом. Думаю, да.   -  person Marc L. Allen    schedule 01.05.2019
comment
@MarcL.Allen: правильно, что для того, чтобы тип был членом класса типов Fractional, он должен быть членом класса типов Num, следовательно, это означает, что Fractional более специфичен, чем классу типов Num, так как другие члены (например, Int) могут быть членами класса типов Num, но не класса типов Fractional.   -  person Willem Van Onsem    schedule 01.05.2019
comment
@MarcL.Allen: ввод переменных - неплохой ход, обычно переменные верхнего уровня вводятся во фрагменте кода Haskell. Однако похоже, что вы думаете, что это какое-то объявление переменной, как в Java и т. Д., Например, int i; i = 2, это не так.   -  person Willem Van Onsem    schedule 01.05.2019
comment
Краткий ответ: общий не означает нетипизированный.   -  person Fyodor Soikin    schedule 01.05.2019
comment
даже перед типами = не присвоение. это определение.   -  person Will Ness    schedule 01.05.2019
comment
@DanielWagner Я не собираюсь придавать этому большого значения, но я действительно не думаю, что это дублирующий вопрос. Мой вопрос больше касается неправильного представления о том, что означает ввод переменной. Хотя связанный вопрос во всей другой информации, которую он предоставляет, включает ответы на этот вопрос, эти два вопроса на самом деле не совпадают.   -  person Marc L. Allen    schedule 02.05.2019
comment
@MarcL.Allen Эмпирическое правило, которое я использую, заключается в том, что если два вопроса дают один и тот же ответ, то они дублируются, даже если они не совсем одинаковые. Когда я прочитал этот вопрос, моим инстинктом было снова написать типы в виде протоколов, поскольку это прекрасно объясняет, что происходит; поэтому я считаю, что эти два вопроса дают один и тот же ответ. (Это просто удача, что вы спрашивали конкретно о Num и Fractional, как связанный вопрос.) См. Также Когда два вопроса считаются дубликатами? обсуждение мета-SO.   -  person Daniel Wagner    schedule 02.05.2019
comment
@DanielWagner Понятно. Однако из третьего абзаца в разделе «Когда два вопроса считаются повторяющимися» я считаю, что эти вопросы просто связаны, даже если вы думали об использовании одного и того же ответа. Ваш ответ дает гораздо больше ответов, чем любой заданный вопрос, поэтому неудивительно, что его можно использовать для разных вопросов.   -  person Marc L. Allen    schedule 02.05.2019
comment
@MarcL.Allen Я тоже думал, что ваш вопрос больше касается сути параметрического полиморфизма, чем конкретных классов типов. но в этом представлении также есть много дубликатов. Я попытаюсь найти несколько ссылок, но вы можете просто искать/просматривать сам тег.   -  person Will Ness    schedule 02.05.2019


Ответы (1)


i :: a; i = 1

Я ожидал, что, поскольку я был напечатан как можно более обобщенно, я мог присвоить ему абсолютно все, что угодно. (Я знаю, что, вероятно, ничего не могу сделать с переменной i, но это не важно.)

Нет, все наоборот. Тип представляет, как это значение может быть использовано позже, т. е. он указывает, что пользователь может использовать i, притворяясь, что это значение любого типа, который может потребоваться в данный момент. По сути, пользователь выбирает тип a, и код, определяющий i :: a, должен соответствовать любому такому выбору пользователя.

(Кстати, мы обычно называем i = 1 привязкой или определением, а не присваиванием, поскольку это означает, что мы можем переназначить позже.)

gg :: Num a => a; gg = g 1.3
(error)

Здесь действует тот же принцип. gg претендует на любой числовой тип, который может понадобиться пользователю, но если пользователь позже выберет, скажем, Int, определение g 1.3 не подходит Int.

Пользователь может выбрать тип, используя явную сигнатуру (print (gg :: Int)) или поместив его в контекст, который определяет тип (print (length "hello" + gg) заставляет Int, поскольку length возвращает Int).

Если вы знакомы с дженериками в некоторых других языках, вы можете провести сравнение с этим кодом:

-- Haskell
i :: a
i = 1            -- type error

-- pseudo-Java
<A> A getI() {
  return 1;      -- type error
}

С более теоретической точки зрения вы думаете о неправильном квантификаторе. Когда вы пишете i :: a, вы думаете, что i :: exists a . a (не настоящий тип Haskell), который читается как i, является значением некоторого типа (выбранного во время определения). Вместо этого в Haskell i :: a означает i :: forall a . a, которое читается как i, является значением всех типов (любого типа, который может понадобиться при использовании). Следовательно, все сводится к существующим и forall или к тому, кто выбирает тип a на самом деле.

person chi    schedule 01.05.2019
comment
Ой ой! Подождите секунду... может грядет прозрение.... - person Marc L. Allen; 01.05.2019
comment
Итак... i :: a означает, что я могу подключить i к любой функции независимо от ее требований? Что i :: a говорит, что i реализует все возможные классы типов? (EDIT: похоже, это соответствует вашему редактированию forall) - person Marc L. Allen; 01.05.2019
comment
@MarcL.Allen Грубо говоря, да. Это означает, что i можно использовать там, где вам нужны Int, Bool, String, Maybe [(Int, Bool)] -> Char или любой другой тип. Для классов типов: если класс типов не пустой, т.е. существует некоторый тип T, реализующий методы класса (instance C T where ...), то i можно использовать как значение T. Обратите внимание, что значения принадлежат типам, а типы принадлежат классам типов. - person chi; 01.05.2019
comment
@MarcL.Allen Да, i :: a подразумевает, что я могу подключить i к любой функции независимо от ее требований. Что касается i :: a, говорит, что i реализует все возможные классы типов, на этом я даю громкий вид. Я думаю, что в этом утверждении есть зерно истины, но ваша формулировка кажется немного неточной; находится ли вещь в классе типа или нет, является свойством типов, но i — это вычисление, а не тип. - person Daniel Wagner; 01.05.2019
comment
@DanielWagner Не удивлен ... все еще достаточно рано в процессе обучения, что мой язык будет неточным из-за непонимания более тонких моментов (например, назначение по сравнению с определением) - person Marc L. Allen; 01.05.2019
comment
@DanielWagner, можем ли мы всегда использовать вычисления для чистых значений Haskell (после Wadler после Turner) и зарезервировать вычисления для монадических значений (после Могги)? непоследовательная терминология - одна из причин, по которой у людей могут возникнуть трудности с изучением Haskell. - person Will Ness; 01.05.2019
comment
ключевое значение имеет различие между назначением и определением. с назначением ожидания ОП были бы вполне разумными. но поскольку мы определяем значение (и используем имя только для ссылки на это значение), тип — это значение. > атрибут, а не имя. - person Will Ness; 01.05.2019
comment
@WillNess Не могу не согласиться, особенно с вашим первым комментарием. Например, мне кажется, что переменные (которые, я думаю, вы называете чистыми значениями Haskell) — это не что иное, как функции Haskell, не принимающие аргументов. Это тоже может быть неправильно, но я думаю, что это ближе, чем переменные. Если бы я знал это, этого и других вопросов можно было бы полностью избежать. - person Marc L. Allen; 02.05.2019
comment
нет нет. есть много вещей, в том числе и от больших пушек, о том, как значения Haskell являются не нуль-функциями. Во всяком случае, я бы предпочел имена вместо переменных, именованных значений или привязок. Но с переменными тоже все в порядке. В математике они слишком известны как переменные и также являются константами. --- re: чистые значения, все является чистым значением в Haskell. x = 7+8 и y = print x оба есть. Но точка зрения @Daniel заключалась в том, что из-за лени x не сразу является значением - за этим стоит целый расчет. (это то, что вы тоже имели в виду, я полагаю). 1/ - person Will Ness; 02.05.2019
comment
Он просто использовал то другое слово, которое я предпочел бы использовать исключительно для print x подобных вещей. т.е. потенциально эффективные вещи. Различие тонкое, Maybe Int в любом случае чисто, меняется только наша интерпретация. Но с IO разница реальна. print x — это значение IO (), обозначающее вычисление ввода-вывода. это то, что я имел в виду. (и я дал ссылки, поддерживающие этот выбор терминологии) /2 - person Will Ness; 02.05.2019