Подтвердить что ты не робот

Пожалуйста, подтвердите или исправьте мою "английскую интерпретацию" этого фрагмента кода Haskell

Я разработчик С#, который работает через "Real World Haskell" , чтобы действительно понимать функциональное программирование, чтобы, когда я узнаю F #, я действительно буду это разбираться, а не просто "писать код С# в F #", так сказать.

Хорошо, сегодня я наткнулся на пример, который, как я думал, я понял 3 раза, только чтобы увидеть то, что я пропустил, обновить мою интерпретацию и рекурсировать (и проклясть тоже, поверьте мне).

Теперь я считаю, что действительно понимаю это, и я написал подробный "английский перевод" ниже. Можете ли вы, гуру Haskell, подтвердить это понимание или указать, что я пропустил?

Примечание. Фрагмент кода Haskell (цитируемый непосредственно из книги) определяет настраиваемый тип, который должен быть изоморфен встроенному типу списка Haskell.

Фрагмент кода Haskell

data List a = Cons a (List a)
              | Nil
              defining Show

EDIT: после некоторых ответов я вижу одно недоразумение, которое я сделал, но я не совсем понимаю правила "разбора" Хаскелла, которые исправили бы эту ошибку. Поэтому я включил мою оригинальную (неправильную) интерпретацию ниже, а затем исправление, за которым следует вопрос, который до сих пор остается неясным для меня.

EDIT: Вот моя оригинальная (неправильная) "английская интерпретация" фрагмента

  • Я определяю тип, называемый "Список".
  • Тип списка параметризуется. Он имеет один параметр типа.
  • Есть два конструктора значений, которые можно использовать для создания экземпляров списка. Один конструктор значений называется "Nil", а другой конструктор значений называется "Cons" .
  • Если вы используете конструктор значения "Nil", то нет полей.
  • Конструктор значения "Cons" имеет параметр одного типа.
  • Если вы используете конструктор значений "Минус" , есть два поля, которые должны быть предоставлены. Первым обязательным полем является экземпляр List. Второе обязательное поле является экземпляром a.
  • (Я намеренно пропустил что-либо о "определении Show", потому что это не часть того, что я хочу сосредоточить прямо сейчас).

Исправленная интерпретация будет следующей (изменения в BOLD)

  1. Я определяю тип, называемый "Список".
  2. Тип списка параметризуется. Это имеет параметр одного типа.
  3. Есть два конструктора значений  которые могут быть использованы для создания экземпляров  списка. Один конструктор значений   "Nil" и другое значение  конструктор называется "Минусы".
  4. Если вы используете конструктор значения "Nil", то нет полей.

    5. (эта строка была удалена... это не точно) Конструктор значения "Минус" имеет один параметр типа.

  5. Если вы используете конструктор значения "Cons" , есть 2 поля  которые должны быть предоставлены. Первый  обязательное поле является экземпляром a.  Второе обязательное поле - это  экземпляр "List-of-a".

  6. (Я намеренно пропустил что-либо о "определении Show", потому что это не часть того, что я хочу сосредоточить прямо сейчас).

Вопрос, который остается неясным

Первоначальная путаница заключалась в части фрагмента, который читает "Cons a (List a)". На самом деле, это та часть, которая до сих пор не ясна для меня.

Люди указали, что каждый элемент в строке после токена "Минус" - это тип, а не значение. Таким образом, это означает, что эта строка говорит: "Конструктор значения Cons имеет 2 поля: один из типов" a ", а другой тип" list-of-a "."

Это очень полезно знать. Однако что-то еще неясно. Когда я создаю экземпляры с использованием конструктора Cons Cons, эти экземпляры "интерпретируют" первый "a" как значение "помещают значение, переданное здесь". Но они не интерпретируют второй "а" таким же образом.

Например, рассмотрим этот сеанс GHCI:

*Main> Cons 0 Nil
Cons 0 Nil
*Main> Cons 1 it
Cons 1 (Cons 0 Nil)
*Main> 

Когда я печатаю "Cons 0 Nil", он использует конструктор значения "Cons" для создания экземпляра List. Начиная с 0, он узнает, что параметр типа "Integer". Пока нет путаницы.

Однако он также определяет, что значение первого поля Cons составляет 0. Но он ничего не определяет о значении второго поля... он определяет только, что второе поле имеет тип "Integer List",.

Итак, мой вопрос: почему "а" в первом поле означает "тип этого поля:" а ", а значение этого поля" а ", а" а "во втором поле означает только" Тип этого поля - "Список a"?

EDIT: Я считаю, что теперь я видел свет, благодаря нескольким ответам. Позвольте мне сформулировать это здесь. (И если каким-то образом это по-прежнему некорректно, пожалуйста, дайте мне знать!)

В фрагменте "Cons a (List a)" мы говорим, что конструктор значения "Cons" имеет два поля и первое поле имеет тип "a" и второе поле имеет тип "a" , Список '.

Это все, что мы говорим! В частности, мы говорим НИЧЕГО о значениях! Это ключевой момент, который мне не хватало.

Позже мы хотим создать экземпляр, используя конструктор значения "Cons" . Мы вводим это в интерпретатор: "Минусы 0 Нил". Этот явно сообщает конструктору Cons Cons использовать 0 для значения первого поля и использовать Nil как значение для второго поля.

И все это к нему. Как только вы знаете, что определение конструктора значения указывает только на типы, все становится ясным.

Спасибо всем за полезные ответы. И, как я сказал, если что-то еще не работает, пожалуйста, непременно расскажите мне об этом. Спасибо.

4b9b3361

Ответ 1

  • Конструктор значения "Cons" имеет параметр одного типа.

Нет: вы уже параметризировали его, когда вы объявили data List a. Одним из эффективных свойств этого является то, что если у меня есть Nil:: List Int, я не могу его заменить с помощью Nil:: List Char.

  • Если вы используете конструктор значений "Минус", есть два поля, которые должны быть предоставлены. Первым обязательным полем является экземпляр List. Второе обязательное поле является экземпляром a.

У вас есть обмен: первое обязательное поле - это экземпляр a, второе поле - экземпляр List.

Эта глава Real World Haskell может представлять интерес.

Спасибо. Это та глава, на которой я сейчас. Итак... когда код говорит "Cons a (List a)", я подумал, что часть "Cons a" утверждает, что конструктор значения Cons был параметризован. Они еще не рассмотрели синтаксис для параметризованных типов, поэтому я догадался, что для синтаксиса требуется повторное утверждение "a", если вы намереваетесь использовать a. Но ты говоришь, что это не нужно? И поэтому что не то, что означает "а" ?

Неа. Как только мы объявляем параметр в нашем типе, мы снова используем его, чтобы сказать "этот тип должен использоваться там". Это немного похоже на подпись типа a -> b -> a: a является параметризацией типа, но тогда я должен использовать то же самое, что и возвращаемое значение.

ОК, но это путает. Кажется, что первое "а" означает "первое поле является экземпляром",

Нет, это не так. Это просто означает, что тип данных параметризуется над некоторым типом a.

и это ТАКЖЕ означает, что "первое поле имеет то же значение, что и значение, которое они передали для a". Другими словами, он задает значение типа AND.

Нет, это также неверно.

Вот поучительный пример, синтаксис которого вы могли или не видели раньше:

foo :: Num a => a -> a

Это довольно стандартная сигнатура для функции, которая принимает число и что-то делает с ним, и дает вам еще один номер. То, что я на самом деле подразумеваю под "числом" в Haskell-talk, однако, является некоторым произвольным типом "a", который реализует класс "Num".

Таким образом, это анализирует английский:

Пусть a указывает тип, реализующий класс типа Num, то сигнатура этого метода является одним параметром с типом a и возвращаемым значением типа a

Что-то подобное происходит с данными.

Мне также приходит в голову, что экземпляр List в спецификации Cons также сбивает вас с толку: будьте очень внимательны при разборе этого: в то время как Cons указывает конструктор, который является в основном шаблоном, который Haskell собирается обернуть данными (Список a) выглядит как конструктор, но на самом деле просто тип, например Int или Double. a является типом, а не значением в любом смысле этого слова.

Изменить: В ответ на самое последнее изменение.

Я думаю, что вскрытие сначала требуется. Затем я буду рассматривать ваши вопросы по пунктам.

Конструкторы данных Haskell немного странны, потому что вы определяете подпись конструктора, и вам не нужно создавать какие-либо другие леса. В Datatypes в Haskell нет понятия членной переменной. (Обратите внимание: существует альтернативный синтаксис, что этот способ мышления более поддается, но пусть игнорирует это на данный момент).

Другое дело, что код Haskell плотный; его подпись типа такова. Поэтому ожидаем увидеть один и тот же символ, повторно используемый в разных контекстах. Здесь также играют важную роль.

Итак, вернемся к вашему типу:

data List a = Cons a (List a)
              | Nil

Я разбиваю это на несколько частей:

data List a

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

Cons a (List a) |
Nil

Это имя конструктора данных. Это НЕ тип. Мы можем, однако, сопоставить шаблон для него, ala:

foo :: List a -> Bool
foo Nil = True

Обратите внимание, что List a - это тип в сигнатуре, а Nil - как конструктор данных, так и "штука", для которой мы рисуем соответствие.

Cons a (List a)

Это типы значений, которые мы вставляем в конструктор. Минусы имеют две записи: одна имеет тип a, а одна имеет тип List a.

Итак, мой вопрос: почему "а" в первом поле означает "тип этого поля:" а ", а значение этого поля" а ", а" а "во втором поле означает только" Тип этого поля - "Список a"?

Простой: не думайте об этом, указав тип; подумайте, что Haskell выводит на экран этот тип. Итак, по нашим намерениям и целям, мы просто торчаем там 0 и Nil во втором разделе. Затем, Haskell смотрит на наш код и думает:

  • Хм, интересно, какой тип Cons 0 Nil -
  • Ну, Cons - это конструктор данных для списка a. Интересно, какой тип List a является
  • Ну, a используется в первом параметре, так как первым параметром является Int (другое упрощение: 0 - это действительно странная вещь, которая классифицируется как Num), так что значение a равно Num
  • Эй, ну, это также означает, что тип Nil - это List Int, хотя там ничего нет, что фактически скажет, что

(Обратите внимание: это не так, как это реализовано. Haskell может делать много странных вещей, в то время как типы вывода, частично из-за чего сообщения об ошибках сосут.)

Ответ 2

5 неверно, и я сказал бы 6 следующим образом, чтобы заменить оба:

Cons {1} a {2} (Список a) {3} - это конструктор с именем Cons (часть до {1}) для значения типа List a (список данных), для которого требуется два значения: один типа a (часть между {1} ​​и {2}) и один из типов List a (часть между {2} и {3}).

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

Ответ 3

Аналогов обычно не хватает разными способами, но поскольку вы знаете С#, я думал, что это может быть полезно.

Вот как я бы описал определение List a в С#, возможно, это очистит некоторые вещи (или, скорее, смущает вас еще больше).

class List<A>
{
}

class Nil<A> : List<A>
{
    public Nil() {}
}

class Cons<A> : List<A>
{
    public A Head;
    public List<A> Tail;

    public Cons(A head, List<A> tail)
    {
        this.Head = head;
        this.Tail = tail;
    }
}

Как вы можете видеть;

  • тип List в качестве параметра одного типа (<A>),
  • Конструктор Nil не имеет параметров,
  • а конструктор Cons имеет два параметра: значение типа A и список типов List<A>

Теперь, в Haskell, Nil и Cons являются просто конструкторами для типа данных List a, в С# они также сами по себе являются типами, поэтому, когда аналогия терпит неудачу.

Но я надеюсь, что это даст вам некоторое интуитивное представление о том, что представляют собой разные A.

(И, пожалуйста, прокомментируйте, как это ужасное сравнение не оправдывает типы данных Haskell.)

Ответ 4

Cons a (List a)

Первое поле Cons - это значение типа "a". Второй - это значение типа "List a", то есть список, параметризованный тем же типом, что и параметр текущего списка.

Ответ 5

Да, синтаксис данных немного запутан, потому что он называет имена и типы и на самом деле не делает синтаксического различия между ними. В частности, в определении конструктора, например:

Cons a (List a)

Первое слово - это имя конструктора; каждое другое слово - это имя некоторого предопределенного типа. Таким образом, как a, так и List a уже находятся в области видимости (a было введено в область видимости a в "списке данных a", и вы говорите, что это типы параметров. Их роль может быть лучше продемонстрирована путем указания то же самое, используя синтаксис записи:

Cons { head :: a, tail :: List a }

т.е. значение типа List Int, если оно было сконструировано с конструктором Cons, имеет два поля: Int и List Int. Если он был построен с помощью Nil, он не имеет полей.

Ответ 6

Когда я набираю "Cons 0 Nil", он использует конструктор значения "Cons" для создания экземпляра списка. Начиная с 0, он узнает, что параметр типа "Integer". Пока нет путаницы.

Однако он также определяет, что значение первого поля Cons составляет 0. Но он ничего не определяет о значении второго поля... он определяет только, что второе поле имеет тип "Integer List",.

Нет, он определяет, что второе значение поля Nil. Учитывая ваше определение, Nil является значением типа List a. Поэтому так Cons 0 Nil. И в Cons 1 it второе значение поля it; т.е. Cons 0 Nil. Это как раз показывает REPL: Cons 1 (Cons 0 Nil).

Ответ 7

Я посмотрел на ваш отредактированный вопрос.

Когда я создаю экземпляры с помощью Cons конструктор значений, те экземпляры "интерпретировать" первое "а" как значение "поместите здесь значение.

В "Cons a (List a)", как "a" , так и "List a" являются типами. Я не понимаю, что с этим связано "ценность".

Когда я печатаю "Cons 0 Nil", он использует Конструктор значения "Минус" для создания экземпляр списка. Начиная с 0, он учится что параметр типа "Integer". Пока нет путаницы.

Однако он также определяет, что значение первого поля "Против" равно 0. Но он ничего не определяет значение второго поля... это только определяет, что второе поле имеет тип "List Integer".

Значение второго поля Nil.

Итак, мой вопрос: почему "а" в первое поле означает "тип этого поле "a" , и значение этого поле "a" , а "a" - во втором поле означает только "тип этого поле" Список"?

"a" в первом поле означает "тип этого поля -" a ". "Список a" во втором поле означает" тип этого поля: "Список a". В случае "Cons 0 Nil" выше "a" выводится как "Integer". Итак, "Cons a (List a)" становится "Cons Integer (List Integer)". 0 - значение типа Integer. Nil - это значение типа "List Integer".

значение этого поля равно 'a'

Я не понимаю, что вы подразумеваете под этим. 'a' - это переменная типа; что он имеет отношение к значениям?

Ответ 8

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

Еще одна "забавная" характеристика Haskell заключается в том, что она явно (или неявно) не оценивает аргументы функции, , даже если этот аргумент находится в круглых скобках. Позвольте мне сказать, что несколько иначе: функции Haskell не вычисляют аргументы в круглых скобках перед другими аргументами. Аргументы помещаются в круглые скобки только для целей группировки, а не для их оценки "первым". Haskell присваивает аргументы ( "применяет" ) функцию с более высоким приоритетом, чем любая другая операция - даже выше, чем применение неявной функции одного из своих собственных аргументов. Вот почему конструктор Cons имеет parens вокруг второго аргумента, (List a) -, чтобы сообщить компилятору, что Cons имеет два аргумента, а не три. Скобки предназначены только для группировки, а не для приоритета!

Как часть побочной темы, будьте осторожны с типами в F #. Поскольку F # имеет свои корни в ML, его параметризованные типы имеют передние параметры - int list, а не (List Int) назад! Haskell делает это по-другому, потому что он так же, как Haskell выполняет функции - сначала функцию, затем аргументы функции. Это поощряет общую схему использования и объясняет, почему типы Haskell и конструкторы значений являются заглавными - чтобы напомнить вам, что вы имеете дело с вещью типа/класса.

Хорошо, я закончил. спасибо за то, что позволил мне положить этот огромный Wall O 'Text на вашу собственность...