#haskell
#haskell
Вопрос:
Раскрытие информации: я программист Scala, изучающий Haskell.
Я хотел бы немного лучше понять поведение переменной типа.
В отличие от Scala, кажется, что мы можем объявить переменную универсального типа (т.Е. переменную типа). Это общее не ограничивается функцией. Однако я не могу понять следующие недостатки и хотел бы получить некоторые объяснения по этому поводу.
Почему следующий код работает над функцией:
g :: a -> a -- a can take the concrete type Num a => a or something else like Bool
g x = x
g 10
--g 10 :: Num a => a
g True
--g True :: Bool
Однако с переменной, в отличие от функции, происходит следующая странная вещь
e :: a
e = 1
-- No instance for (Num a) arising from the literal ‘1’
-- Possible fix:
-- add (Num a) to the context of
-- the type signature for:
-- e1 :: forall a. a
-- In the expression: 1
-- In an equation for ‘e1’: e1 = 1
То же самое, конечно, если я введу следующее в REPL
e :: a; e = 1
Q1) Из этого, казалось бы, у нас есть разница в поведении универсального типа (т.Е. Переменной типа) между переменной и функцией? Почему это так? или что я еще не вижу или не получаю?
Теперь странная часть
Если в Repl, однако, я делаю
e :: a; e = undefined
и затем
e = 1
тогда я получаю
:t e
-- e1 :: Num p => p
Таким образом, используя undefined в REPL «по крайней мере», я возвращаю поведение универсального типа для функции.
Q2) Что на самом деле здесь происходит? Для меня все это звучит не очень логично. Кто-нибудь может объяснить, пожалуйста?
Комментарии:
1.
e :: a
средстваe
могут быть любого типа. Не любой тип, который вы хотите, а любой тип, который может понадобиться любому коду, который может быть использованe
.2. Хорошо, давайте посмотрим. Если вы напишете
g :: a -> a; g x = 42 x
, вы получите ошибку, мало чем отличающуюся от ошибки, которую вы получаетеe::a
. Имеет ли эта ошибка смысл для вас?3. Вы можете просмотреть это таким образом.
g :: a -> a
имеет много типов.Int->Int
,String->String
иBool->Bool
являются всеми типамиg
,a->a
является просто наиболее общим из этих типов. Уравнениеg x = x
не противоречит ни одному из этих типов. Аналогично,e :: a
имеет много типов (на самом деле все возможные типы), включаяInt
,String
,Bool
иInt->Int
(так что вы можете легально написатьe == "abc"
илиe 42
). Однако определениеa = 1
не соответствует большинству из этих типов.4. Небольшая поправка, старое определение не забыто, но сделано невидимым (оно по-прежнему доступно для других определений, которые ссылаются на старое определение).
5. Очень хорошее разъяснение, сэр!!! Спасибо. Я бы посоветовал, если у вас есть время, дать ответ на этот вопрос. Итак, (1) Я могу проголосовать, (2) и другим на моем пути, которые, возможно, сначала не поймут это, как я, было бы полезно получить официальный ответ.
Ответ №1:
Переменные типа в Haskell универсально определяются количественно. Это означает, что фактический тип g
g :: ∀ a . a -> a
Квантификатор обычно неявный, но вы можете фактически указать его в исходном коде Haskell (as forall
), если вы включите определенные расширения GHC.
Что это значит? Ну, буквально, g
имеет тип a -> a
для всех возможных значений a
. То есть, g
это a Bool->Boll
и a String->String
и Int->Int
функция. Он имеет все эти типы.
Когда вы пишете g x = x
, вы не нарушаете обещание, данное типом g
, а именно, что аргумент может быть любого типа, и результат должен быть того же типа. Действительно, результат x
и аргумент также x
, и x
, очевидно, имеет тот же тип, x
что и .
Однако, если вы пишете g x = x 42
, это нарушает обещание. x
не может быть типа Bool
или String
больше. Поэтому компилятор будет жаловаться.
Теперь обозначение
e :: a
очень похожим образом следует интерпретировать как
e :: ∀ a . a
Это означает e
, что имеет тип a
для всех возможных значений a
. То есть у него есть все возможные типы: Int
и String
и Bool
и Int->Int
и бесконечно много других.
Вы могли бы удовлетворить тип, определив e
как
e = undefined
(и действительно, это, по сути, единственный способ удовлетворить его). Однако, если вы напишете
e = 1
вы нарушаете обещание, данное типом e
. Таким образом, вы получите ошибку того же типа, что и выше.
Если вы пишете e = undefined
и после e = 1
этого, то в REPL второе определение затеняет первое, поэтому у вас есть две разные переменные с именами e
, каждая из которых имеет свой собственный тип. Если вы используете REPL, вы можете переопределить любое имя в любое удобное для вас время, для этого и существует REPL! Это, конечно, невозможно в обычном модуле Haskell.