Типы и переменные типов в Haskell - PullRequest
0 голосов
/ 27 февраля 2019

Царапая на поверхности системы типов Haskell, запустил это:

Prelude> e = []
Prelude> ec = tail "a"
Prelude> en = tail [1]
Prelude> :t e
e :: [a]
Prelude> :t ec
ec :: [Char]
Prelude> :t en
en :: Num a => [a]
Prelude> en == e
True
Prelude> ec == e
True

Каким-то образом, несмотря на то, что en и ec имеют разные типы, они оба тестируют Trueна == е .Я говорю «как-то» не потому, что я удивлен (я не удивлен), а потому, что я не знаю, как называется правило / механизм, который позволяет это.Это похоже на то, что переменной типа "a" в выражении "[] == en" разрешено принимать значение "Num" для оценки.И точно так же при тестировании с «[] == ec», он может стать «Char».

Причина, по которой я не уверен, что моя интерпретация верна, такова:

Prelude> (en == e) && (ec == e)
True

, поскольку интуитивно это подразумевает, что в одном и том же выражении e принимает оба значения Num и Char "одновременно" (по крайней мере, так я использую для интерпретации семантики &&).Разве «допущение» Чар действует только во время оценки (ec == e) и (en == e) оценивается независимо, в отдельном ... сокращении?(Я предполагаю здесь терминологию).

А потом приходит следующее:

Prelude> en == es

<interactive>:80:1: error:
    • No instance for (Num Char) arising from a use of ‘en’
    • In the first argument of ‘(==)’, namely ‘en’
      In the expression: en == es
      In an equation for ‘it’: it = en == es
Prelude> es == en

<interactive>:81:7: error:
    • No instance for (Num Char) arising from a use of ‘en’
    • In the second argument of ‘(==)’, namely ‘en’
      In the expression: es == en
      In an equation for ‘it’: it = es == en

Не удивительно исключением, но удивлено, что в обоих тестах сообщение об ошибке жалуется на "использование 'en' "- и не имеет значения, является ли это первым или вторым операндом.

Возможно, необходимо извлечь важный урок о системе типов Haskell.Спасибо за ваше время!

Ответы [ 2 ]

0 голосов
/ 27 февраля 2019

Иногда удобно рассматривать полиморфные функции как функции, принимающие явные аргументы типа .Давайте рассмотрим полиморфную тождественную функцию в качестве примера.

id :: forall a . a -> a
id x = x

Мы можем думать об этой функции следующим образом:

  • сначала функция принимает в качестве входных данных аргумент типа a
  • секунда, функция принимает на вход значение x ранее выбранного типа a
  • last, функция возвращает x (типа a)

Вот возможный вызов:

id @Bool True

Выше синтаксис @Bool передает Bool для первого аргумента (аргумент типа a), в то время как True передаетсяв качестве второго аргумента (x типа a = Bool).

Несколько других:

id @Int 42
id @String "hello"
id @(Int, Bool) (3, True)

Мы можем даже частично применить id, передав только аргумент типа:

id @Int       :: Int -> Int
id @String    :: String -> String
...

Теперь обратите внимание, что в большинстве случаев Haskell позволяет нам опустить аргумент типа.Т.е. мы можем написать id "hello" и GHC попытается вывести аргумент отсутствующего типа.Примерно это работает следующим образом: id "hello" преобразуется в id @t "hello" для некоторого неизвестного типа t, затем в соответствии с типом id этот вызов может проверять тип только если "hello" :: t, а с "hello" :: String мыможно сделать вывод t = String.

Вывод типа очень распространен в Haskell.Программисты редко указывают свои аргументы типа и позволяют GHC выполнять свою работу.

В вашем случае:

e :: forall a . [a]
e = []
ec :: [Char]
ec = tail "1"
en :: [Int]
en = tail [1]

Переменная e связана с полиморфным значением.То есть фактически это функция сортировки, которая принимает аргумент типа a (который также может быть опущен) и возвращает список типа [a].

Вместо этого ec не делаетпринять любой тип аргумента.Это простой список типа [Char].Аналогично для en.

Затем мы можем использовать

ec == (e @Char)    -- both of type [Char]
en == (e @Int)     -- both of type [Int]

Или мы можем позволить механизму вывода типов определять неявные аргументы типа

ec == e     -- @Char inferred
en == e     -- @Int inferred

Последнийможет вводить в заблуждение, поскольку кажется, что ec,e,en должен иметь тот же тип.На самом деле это не так, поскольку выводятся различные неявные аргументы типа.

0 голосов
/ 27 февраля 2019

Когда мы говорим, что 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

...