Когда мы говорим, что e :: [a]
, это означает, что e
- это список элементов любого типа.Какого вида?Любой тип!Какой бы тип вам ни понадобился в данный момент.
Если вы используете язык, не относящийся к ML, это может быть немного легче понять, посмотрев сначала на функцию (а не на значение).Учтите это:
f x = [x]
Тип этой функции: f :: a -> [a]
.Это примерно означает, что эта функция работает для любого типа a
.Вы дадите ему значение этого типа, и он вернет вам список с элементами этого типа.Какого вида?Любой тип!Что бы вам ни понадобилось.
Когда я вызываю эту функцию, я фактически выбираю , какой тип мне нужен в данный момент.Если я называю это как f 'x'
, я выбираю a = Char
, и если я называю это как f True
, я выбираю a = Bool
.Поэтому важным моментом здесь является то, что тот, кто вызывает функцию, выбирает параметр типа .
Но мне не нужно выбирать его раз и навсегда.Вместо этого я выбираю параметр типа каждый раз, когда вызываю функцию .Учтите это:
pair = (f 'x', f True)
Здесь я дважды звоню f
и каждый раз выбираю разные параметры типа - первый раз выбираю a = Char
, а второй - a = Bool
.
Хорошо, теперь следующий шаг: когда я выбираю параметр типа, я могу сделать это несколькими способами.В приведенном выше примере я выбираю его, передавая параметр значения нужного мне типа.Но другой способ - указать тип результата , который я хочу .Учтите это:
g x = []
a :: [Int]
a = g 0
b :: [Char]
b = g 42
Здесь функция g
игнорирует свой параметр, поэтому нет никакой связи между ее типом и результатом g
.Но я все еще могу выбрать тип этого результата, ограничив его окружающим контекстом.
И теперь, ментальный скачок: функция без каких-либо параметров (она же «значение»)не сильно отличается от функции с параметрами .У него просто нулевые параметры, вот и все.
Если значение имеет параметры типа (например, ваше значение e
), я могу выбирать этот параметр типа каждый раз, когда я "вызываю" это значение, так же легко, как если бы это была функция.Таким образом, в выражении e == ec && e == en
вы просто «вызываете» значение e
дважды, выбирая различные параметры типа для каждого вызова - так же, как я делал в примере pair
выше.
Путаница в отношении Num
- это совсем другое дело.
Видите ли, Num
не тип.Это тип класса .Классы типов являются подобными интерфейсами в Java или C #, за исключением того, что вы можете объявить их позже , необязательно вместе с типом, который их реализует.
Таким образом, подпись en :: Num a => [a]
означает, что en
- это список с элементами любого типа, при условии, что этот тип реализует ("имеет экземпляр") класс типов Num
.
И способ вывода типа вHaskell работает так: компилятор сначала определяет наиболее конкретные типы, которые он может, а затем пытается найти реализации («экземпляры») требуемых классов типов для этих типов.
В вашем случае компилятор видит, чтоen :: [a]
сравнивается с ec :: [Char]
и показывает: «О, я знаю: a
должно быть Char
!»Затем он находит экземпляры класса и замечает, что a
должен иметь экземпляр Num
, а поскольку a
равен Char
, из этого следует, что Char
должен иметь экземпляр Num
.Но это не так, и поэтому компилятор жалуется: «не могу найти (Num Char)»
Что касается «возникающего при использовании en
» - ну, это потому, что en
являетсяПричина, по которой требуется экземпляр Num
.en
- это та, которая имеет Num
в своей сигнатуре типа, поэтому именно ее наличие вызывает требование Num