Почему бы не быть зависимым типом?


161

Я видел несколько источников, повторяющих мнение, что «Haskell постепенно становится типизированным языком». Предполагается, что с появлением все большего количества языковых расширений Haskell движется в этом общем направлении, но пока не существует.

Есть две вещи, которые я хотел бы знать. Первый, довольно просто, что на самом деле означает «быть языком с зависимой типизацией» ? (Надеюсь, не будучи слишком техническим об этом.)

Второй вопрос ... в чем недостаток? Я имею в виду, что люди знают, что мы движемся в этом направлении, поэтому должно быть какое-то преимущество. И все же, мы еще не там, поэтому, должно быть, есть некоторые недостатки, которые мешают людям идти до конца. У меня складывается впечатление, что проблема заключается в резком увеличении сложности. Но не совсем понимая, что такое зависимая типизация, я точно не знаю.

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


10
Проще говоря, вы можете писать типы, которые зависят от условий (вычислений). Этого достаточно, чтобы указать типы для каждого аспекта вашей программы, и, следовательно, означает, что система типов способна полностью специфицировать программу. Проблема состоит в том, что, поскольку типы зависят от вычислений, проверку типов значительно труднее (вообще невозможно).
GManNickG

27
@GManNickG: проверка типов вполне возможна. Вывод типов - это другой вопрос, но опять же, различные расширения GHC давно отказались от идеи, что должна быть возможность вывести все типы.
CA Макканн

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

1
@CAMcCann: Да, моя ошибка.
GManNickG

4
Я не думаю, что кто-то указал на один большой прагматический недостаток: писать доказательства того, что весь ваш код верен, довольно безумно утомительно. Поскольку вы не можете автоматически делать вывод типа (соответствует доказательству теоремы в «мощной» логике), вы должны написать аннотации для вашей программы в форме доказательств. Это очевидно становится раздражающим и трудным сделать через некоторое время, особенно для более сложной монадической магии, которую люди обычно делают в Хаскеле. Самое близкое, что мы приедем в эти дни - это языки, которые делают для нас большую часть этого или дают нам хороший набор примитивов.
Кристофер Мичински

Ответы:


21

Зависимая типизация - это на самом деле просто объединение значений и уровней типов, так что вы можете параметризовать значения для типов (это уже возможно с классами типов и параметрическим полиморфизмом в Haskell) и вы можете параметризовать типы по значениям (строго говоря, в Haskell это пока невозможно) , хотя DataKindsподбирается).

Изменить: Видимо, с этого момента, я был не прав (см. Комментарий @ pigworker). Я сохраню остальную часть этого как запись мифов, которыми я питался. :П


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

Это не обязательно является фундаментальным ограничением, но я лично не знаю ни одного текущего исследования, которое выглядит многообещающим в этом отношении, но которое еще не превратило его в GHC. Если кто-то еще знает больше, я был бы рад исправить.


46
То, что вы говорите, почти полностью ложно. Я не совсем вас виню: это повторяет стандартные мифы как факт. Язык Эдвина Брэди, Идрис, выполняет стирание типов (поскольку поведение типов во время выполнения не зависит от типов) и генерирует довольно стандартное кодирование суперкомбинатора с использованием лямбда-выражения, из которого код генерируется с использованием стандартных методов G-машины.
свиновод

3
Как примечание, кто-то недавно указал мне на этот документ . Из того, что я могу сказать, это сделало бы Хаскель зависимо родственным (т. Е. Язык уровня типов был бы зависимо типизированным), что настолько близко, насколько я могу видеть, что мы получим в ближайшее время.
Пламя Птариена

8
Да, эта статья в большинстве случаев показывает, как сделать типы зависимыми от содержимого уровня типа (и устранить различие типа / вида). Возможное продолжение, которое уже обсуждается, состоит в том, чтобы разрешить фактические зависимые типы функций, но ограничить их аргументы фрагментом языка, который может существовать как на уровне значений, так и на уровне типов (теперь нетривиально благодаря продвижению типов данных). Это исключило бы необходимость создания одноэлементной конструкции, которая в настоящее время делает «подделку» более сложной, чем желательно. Мы постепенно приближаемся к реальному.
свиновод

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

2
@pigworker Есть ли чистое подмножество Haskell, которое всего? Если так, разве мы не могли бы просто использовать это для «фрагмента языка, который может существовать как на уровне значений, так и на уровне типов»? Если нет, что нужно для его производства?
Пламя Птариена

223

Зависит от Хаскеля, сейчас?

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

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

data Nat = Z | S Nat

data Vec :: Nat -> * -> * where
  VNil   :: Vec Z x
  VCons  :: x -> Vec n x -> Vec (S n) x

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

vApply :: Vec n (s -> t) -> Vec n s -> Vec n t
vApply VNil         VNil         = VNil
vApply (VCons f fs) (VCons s ss) = VCons (f s) (vApply fs ss)

что приятно Обратите внимание, что длина nявляется чисто статической функцией в этой функции, гарантируя, что входной и выходной векторы имеют одинаковую длину, даже если эта длина не играет роли в выполнении vApply. Напротив, это гораздо сложнее (т.е. невозможно) реализовать функцию , которая делает nкопии данность x(что было бы , pureчтобы vApply«s <*>)

vReplicate :: x -> Vec n x

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

data Natty :: Nat -> * where
  Zy :: Natty Z
  Sy :: Natty n -> Natty (S n)

Для любого продвигаемого типа мы можем построить одноэлементное семейство, индексированное по продвигаемому типу, в котором присутствуют дубликаты его значений во время выполнения. Natty nтип времени выполнения копий уровня типа n :: Nat. Теперь мы можем написать

vReplicate :: Natty n -> x -> Vec n x
vReplicate Zy     x = VNil
vReplicate (Sy n) x = VCons x (vReplicate n x)

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

Что противного? Чего не хватает?

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

class Nattily (n :: Nat) where
  natty :: Natty n
instance Nattily Z where
  natty = Zy
instance Nattily n => Nattily (S n) where
  natty = Sy natty

позволяя нам писать, скажем,

instance Nattily n => Applicative (Vec n) where
  pure = vReplicate natty
  (<*>) = vApply

Это работает, но теперь это означает, что наш оригинальный Natтип породил три копии: вид, одноэлементное семейство и одноэлементный класс. У нас довольно неуклюжий процесс обмена явными Natty nзначениями и Nattily nсловарями. Более того, Nattyэто не так Nat: у нас есть какая-то зависимость от значений времени выполнения, но не от того типа, о котором мы сначала думали. Никакой полностью типизированный язык не делает зависимые типы такими сложными!

Между тем, хотя Natможет быть продвинут, Vecне может. Вы не можете индексировать по индексируемому типу. Полный язык с зависимой типизацией не налагает таких ограничений, и в моей карьере в качестве демонстрации с зависимой типизацией я научился включать примеры двухслойной индексации в свои выступления, просто чтобы научить людей, которые сделали однослойную индексацию трудно, но возможно, не ожидать, что я свернусь, как карточный домик. В чем проблема? Равенство. GADT работают путем преобразования неявных ограничений, которые вы получаете, когда вы предоставляете конструктору конкретный тип возвращаемого значения в явные эквалайзерные требования. Как это.

data Vec (n :: Nat) (x :: *)
  = n ~ Z => VNil
  | forall m. n ~ S m => VCons x (Vec m x)

В каждом из наших двух уравнений обе стороны имеют вид Nat.

Теперь попробуйте тот же перевод для чего-то индексированного по векторам.

data InVec :: x -> Vec n x -> * where
  Here :: InVec z (VCons z zs)
  After :: InVec z ys -> InVec z (VCons y ys)

становится

data InVec (a :: x) (as :: Vec n x)
  = forall m z (zs :: Vec x m). (n ~ S m, as ~ VCons z zs) => Here
  | forall m y z (ys :: Vec x m). (n ~ S m, as ~ VCons y ys) => After (InVec z ys)

и теперь мы формируем эквациональные ограничения между as :: Vec n xи VCons z zs :: Vec (S m) xтам, где две стороны имеют синтаксически различные (но доказуемо равные) виды. Ядро GHC в настоящее время не оборудовано для такой концепции!

Что еще не хватает? Ну, большая часть Haskell отсутствует на уровне типов. Язык терминов, который вы можете продвигать, имеет только переменные и не-GADT конструкторы. Когда у вас есть такие type familyмеханизмы, вы можете писать программы на уровне типов: некоторые из них могут быть очень похожи на функции, которые вы бы сочли нужными для написания на уровне терминов (например, оснащение Natсложением, так что вы можете дать хороший тип для добавления Vec) , но это просто совпадение!

Еще одна вещь, отсутствующая на практике, - это библиотека, которая использует наши новые возможности для индексации типов по значениям. Что делать Functor и Monadстать в этом дивном новом мире? Я думаю об этом, но многое еще предстоит сделать.

Запуск программ уровня типа

Haskell, как и большинство языков программирования с независимой типизацией, имеет две операционные семантики. Есть способ, которым система времени выполнения запускает программы (только закрытые выражения, после стирания типов, высоко оптимизируется), а затем есть способ, которым средство проверки типов запускает программы (ваши семейства типов, ваш "тип класса Prolog" с открытыми выражениями). Для Haskell вы обычно не смешиваете их, потому что выполняемые программы на разных языках. Языки с независимой типизацией имеют отдельные модели времени выполнения и статические модели выполнения для одного и того же языка программ, но не волнуйтесь, модель времени выполнения все еще позволяет вам стирать типы и, действительно, проверять удаление : это то, что извлекает Coqмеханизм дает вам; это по крайней мере то, что делает компилятор Эдвина Брэди (хотя Эдвин стирает излишне дублированные значения, а также типы и доказательства). Фазовое различие, возможно, уже не является различием синтаксической категории , но оно живое и здоровое.

Языки с независимой типизацией, будучи тотальными, позволяют типографу запускать программы без страха перед чем-либо худшим, чем долгое ожидание. Поскольку Haskell становится более зависимым типом, мы сталкиваемся с вопросом о том, какой должна быть его статическая модель выполнения? Один из подходов может заключаться в том, чтобы ограничить статическое выполнение всеми функциями, что даст нам такую ​​же свободу выполнения, но может заставить нас проводить различия (по крайней мере, для кода уровня типа) между данными и кодатами, чтобы мы могли сказать, следует ли обеспечить прекращение или производительность. Но это не единственный подход. Мы свободны в выборе гораздо более слабой модели выполнения, которая не желает запускать программы, за счет того, что из-за вычислений получается меньше уравнений. По сути, именно этим и занимается GHC. Правила набора для ядра GHC не упоминают о запуске программы, но только для проверки доказательств для уравнений. При переводе в ядро ​​средство решения ограничений GHC пытается запустить ваши программы уровня типов, генерируя небольшой серебристый след, свидетельствующий о том, что данное выражение равно его нормальной форме. Этот метод генерации доказательств немного непредсказуем и неизбежно неполон: он борется со страшно выглядящей рекурсией, например, и это, вероятно, разумно. Одна вещь, о которой нам не нужно беспокоиться, это выполнение IO вычислений в проверщике типов: помните, что проверщик типов не должен давать launchMissilesтого же значения, что и система времени выполнения!

Культура Хиндли-Милнера

Система типов Хиндли-Милнера достигает поистине удивительного совпадения четырех различных различий с неблагоприятным культурным побочным эффектом, который многие люди не могут увидеть различие между различиями и считают, что совпадение неизбежно! О чем я говорю?

  • условия против типов
  • явно написанные вещи против неявно написанных вещей
  • присутствие во время выполнения против стирания до выполнения
  • независимая абстракция против зависимой количественной оценки

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

Вам не нужно слишком далеко отклоняться от ванильного Хиндли-Милнера, пока эти различия не выровнялись, и это неплохо . Для начала, у нас могут быть более интересные типы, если мы хотим написать их в нескольких местах. Между тем нам не нужно писать словари классов типов, когда мы используем перегруженные функции, но эти словари обязательно присутствуют (или встроены) во время выполнения. В языках с зависимой типизацией мы ожидаем стереть не только типы во время выполнения, но (как и с классами типов), что некоторые неявно выведенные значения не будут стерты. Например, vReplicateчисловой аргумент часто выводится из типа требуемого вектора, но нам все равно нужно знать его во время выполнения.

Какие языковые варианты дизайна мы должны рассмотреть, потому что эти совпадения больше не имеют места? Например, правильно ли, что на Haskell нет способа forall x. tявно создать экземпляр квантора? Если проверяющий не может угадать, xиспользуя unifi t, у нас нет другого способа сказать, что xдолжно быть.

В более широком смысле, мы не можем трактовать «вывод типа» как монолитное понятие, которое у нас есть либо полностью, либо ничего. Для начала нам нужно отделить аспект «обобщения» (правило «разрешить» Милнера), который в значительной степени опирается на ограничение того, какие типы существуют, чтобы гарантировать, что глупая машина может угадать один, из аспекта «специализации» (переменная Милнера) «Правило), который так же эффективен, как ваш решатель ограничений. Мы можем ожидать, что типы верхнего уровня станут труднее вывести, но информацию о внутренних типах будет довольно легко распространять.

Следующие шаги для Haskell

Мы видим, что уровни типов и типов очень похожи (и они уже имеют внутреннее представление в GHC). Мы могли бы также слить их. Было бы интересно взять, * :: *если мы можем: мы потеряли логическую обоснованность давно, когда мы допустили дно, но правильность типа обычно является более слабым требованием. Мы должны проверить. Если мы должны иметь разные уровни типов, типов и т. Д., Мы можем по крайней мере убедиться, что все на уровне типов и выше всегда можно продвигать. Было бы замечательно просто повторно использовать полиморфизм, который у нас уже есть для типов, а не заново изобретать полиморфизм на уровне вида.

Мы должны упростить и обобщить существующую систему ограничений, допуская гетерогенные уравнения, a ~ bгде виды aи bсинтаксически не идентичны (но могут быть доказаны равными). Это старая техника (в моем тезисе, в прошлом веке), которая значительно облегчает зависимость. Мы могли бы выразить ограничения на выражения в GADT и таким образом ослабить ограничения на то, что можно продвигать.

Мы должны устранить необходимость в одноэлементной конструкции, введя зависимый тип функции pi x :: s -> t. Функция с таким типом может быть применена в явном виде к любому выражению типа, sкоторое находится на пересечении языков типов и терминов (так что переменные, конструкторы, а другие будут описаны позже). Соответствующая лямбда и приложение не будут удалены во время выполнения, поэтому мы сможем написать

vReplicate :: pi n :: Nat -> x -> Vec n x
vReplicate Z     x = VNil
vReplicate (S n) x = VCons x (vReplicate n x)

без замены Natна Natty. Домен piможет быть любого типа, способного к продвижению, поэтому, если GADT можно продвигать, мы можем написать зависимые последовательности кванторов (или «телескопы», как их назвал де Брюйн).

pi n :: Nat -> pi xs :: Vec n x -> ...

на любую длину нам нужно.

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

Слишком сложно?

Зависимые типы заставляют многих нервничать. Они заставляют меня нервничать, но мне нравится нервничать, или, по крайней мере, мне все равно трудно не нервничать. Но это не помогает, что вокруг этой темы существует такой туман невежества. Отчасти это связано с тем, что нам всем еще есть чему поучиться. Но сторонники менее радикальных подходов, как известно, разжигают страх перед зависимыми типами, не всегда следя за тем, чтобы факты были полностью с ними. Я не буду называть имен. Эти «неразрешимые проверки типов», «неполная проверка Тьюринга», «отсутствие различий фаз», «отсутствие стирания типов», «доказательства повсюду» и т. Д. Мифы сохраняются, даже если они являются мусором.

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

Ибо, как и при любом увеличении артикуляции, мы становимся свободными говорить как новые, так и нечестные вещи. Например, есть много грязных способов определения бинарных деревьев поиска, но это не значит, что нет хорошего способа . Важно не предполагать, что плохой опыт не может быть улучшен, даже если это мешает эго признать это. Разработка зависимых определений - это новый навык, который требует обучения, и, будучи программистом на Haskell, вы не станете экспертом автоматически! И даже если некоторые программы являются грязными, почему вы отказываете другим в свободе быть справедливым?

Зачем еще беспокоиться о Haskell?

Мне действительно нравятся зависимые типы, но большинство моих хакерских проектов все еще в Хаскеле. Зачем? У Haskell есть классы типов. У Haskell есть полезные библиотеки. У Haskell есть работоспособное (хотя и далеко не идеальное) лечение программирования с эффектами. У Haskell есть промышленный компилятор силы. Языки с зависимой типизацией находятся на гораздо более ранней стадии развития сообщества и инфраструктуры, но мы добьемся этого с реальным изменением поколений в том, что возможно, например, с помощью метапрограммирования и обобщений типов данных. Но вам просто нужно посмотреть на то, что люди делают в результате шагов Хаскелла к зависимым типам, чтобы увидеть, что можно получить большую выгоду, если продвинуть вперед и нынешнее поколение языков.


6
Я действительно не забочусь о вещах DataKinds пока. Главным образом потому, что я хочу сделать что-то вроде этого fmap read getLine >>= \n -> vReplicate n 0. Как вы заметили, Nattyэто далеко от этого. Кроме того, vReplicate должен быть переведен в реальный массив памяти, что-то вроде newtype SVector n x = SVector (Data.Vector.Vector x), где nимеет вид Nat(или аналог). Возможно, еще одна демонстрационная точка для «понты с зависимым типом»?
Джон Л

7
Не могли бы вы сказать, что вы имеете в виду для идеального подхода к программированию с эффектами?
Стивен Шоу

6
Спасибо за отличную рецензию. Мне бы хотелось увидеть несколько примеров кода с зависимой типизацией, где некоторые данные берутся за пределами программы (например, чтение из файла), чтобы понять, как будет выглядеть продвижение значений для типов в такой настройке. У меня такое ощущение, что все примеры включают векторы (реализованные в виде списков) со статически известными размерами.
Тиббе

4
@pigworker Вы воспринимаете миф как «отсутствие различий по фазе» (другие, я согласен, являются мифами). Но вы не разбирали это в своих статьях и беседах, которые я видел, и в то же время другой человек, которого я уважаю, говорит мне: «теория зависимых типов отличается от типичного компилятора, потому что мы не можем осмысленно разделить фазы проверки типов, компиляции и выполнения». " (см. последний пост Андрея от 8 ноября 2012 г.) По моему опыту, «подделывая это», мы иногда, по крайней мере, стираем различие фаз, хотя нет необходимости стирать его. Не могли бы вы рассказать об этом, если не здесь, то где-нибудь еще?
SCLV

4
@sclv Моя работа не нацелена на миф об отсутствии фазовых различий, а на других. Я рекомендую отсылку Джеймса Маккинна и Эдвина Брэйди «Различия фаз в сборнике эпиграмм» как хорошее место для начала. Но посмотрите также намного более старую работу по Извлечению Программы в Coq. Открытая оценка, выполняемая средством проверки типов, полностью отделена от выполнения посредством извлечения в ML, и ясно, что извлечение исключает типы и доказательства.
свиновод

20

Джон, это еще одно распространенное заблуждение о зависимых типах: они не работают, когда данные доступны только во время выполнения. Вот как вы можете сделать пример getLine:

data Some :: (k -> *) -> * where
  Like :: p x -> Some p

fromInt :: Int -> Some Natty
fromInt 0 = Like Zy
fromInt n = case fromInt (n - 1) of
  Like n -> Like (Sy n)

withZeroes :: (forall n. Vec n Int -> IO a) -> IO a
withZeroes k = do
  Like n <- fmap (fromInt . read) getLine
  k (vReplicate n 0)

*Main> withZeroes print
5
VCons 0 (VCons 0 (VCons 0 (VCons 0 (VCons 0 VNil))))

Редактировать: Хм, это должно было быть комментарием к ответу свиновода. Я явно терплю неудачу в ТАК.


Ваше первое предложение кажется немного странным; Я бы сказал , что точка зависимых типов является то , что они делают работу , когда данные доступны только во время выполнения. Тем не менее, этот метод CPS-стиль не то же самое. Предположим, у вас есть функция Vec Zy -> IO String. Вы не можете использовать его с withZeroes, потому что тип Zyне может быть унифицирован с помощью n. Может быть, вы можете обойти это для одного или двух особых случаев, но это быстро выходит из-под контроля.
Джон Л

Ключ при получении просто типизированного значения (например, String from getLine) и превращении его во что-то с более сильным типом (как Natty n выше) заключается в том, что вам нужно убедить средство проверки типов в том, что вы выполняете необходимые динамические проверки. В вашем примере вы читаете произвольное число, так что forall nимеет смысл. Более точные ограничения могут быть реализованы таким же образом. У вас есть лучший пример, чем Vec Zy(программа все равно должна обрабатывать ввод пользователя 5, а не 0)?
Ульфнорелл

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

1
Ульфнорелл: Извините, мне было непонятно. Предположим, у вас есть одна функция, с которой будет работать, Vec Zy -> IO Stringа другая - для Vec n -> IO String, и вы хотите использовать первую функцию, только если тип соответствует. Да, это возможно, но механизмы его включения неуклюжи. И это очень простая логика; если у вас более сложная логика, это хуже. Также вам может понадобиться переписать много кода в CPS. И у вас все еще нет выражения уровня типа, которое зависит от термина на уровне значения
Джон Л

Ах, я понимаю, что вы говорите. Для этого и нужен Natty, как в vReplicate, где мы делаем разные вещи в зависимости от n. Действительно, это может стать немного неуклюжим. Альтернатива стиля КПСА является работой с фасованными экзистенциалами: zeroes :: IO (Some (Flip Vec Int)).
Ulfnorell

19

Свиновод дает отличную дискуссию о том, почему мы должны ориентироваться на зависимые типы: (а) они классные; (б) они фактически упростили бы многое из того, что Хаскелл уже делает.

Что касается "почему бы и нет?" вопрос, есть пара моментов, я думаю. Первый момент заключается в том, что, хотя базовое понятие, лежащее в основе зависимых типов, является простым (позволяют типам зависеть от значений), последствия этого базового понятия являются как тонкими, так и глубокими. Например, различие между ценностями и типами все еще живо и хорошо; но обсуждать разницу между ними становится далекобольше нюансов, чем у Хиндли - Милнера или Системы F. В некоторой степени это связано с тем, что зависимые типы принципиально сложны (например, логика первого порядка неразрешима). Но я думаю, что большая проблема на самом деле в том, что нам не хватает хорошего словарного запаса для записи и объяснения того, что происходит. По мере того, как все больше и больше людей узнают о зависимых типах, мы разработаем лучший словарный запас, и поэтому все станет легче понимать, даже если основные проблемы все еще остаются сложными.

Второй момент связан с тем, что Haskell растетк зависимым типам. Поскольку мы делаем постепенный прогресс в достижении этой цели, но фактически не достигаем этого, мы застряли с языком, который имеет инкрементные патчи поверх инкрементальных патчей. Подобные вещи происходили на других языках, когда новые идеи стали популярными. Ява не имела (параметрического) полиморфизма; и когда они наконец добавили это, это было очевидно постепенное улучшение с некоторыми утечками абстракции и ограниченной силой. Оказывается, смешивать подтипы и полиморфизм по своей сути сложно; но это не причина, почему Java Generics работает так, как они. Они работают так, как работают, из-за того, что ограничение является постепенным улучшением старых версий Java. То же самое, для дальнейшего в тот день, когда ООП был изобретен, и люди начали писать "цель" C (не путать с Objective-C) и т. Д. Помните, что C ++ начинался под маской строгого надмножества C. Добавление новых парадигм всегда требует нового определения языка или же в конечном итоге приводит к некоторому сложному беспорядку. Моя точка зрения во всем этом заключается в том, что для добавления истинно зависимых типов в Haskell потребуется определенное количество потрошения и реструктуризации языка - если мы собираемся сделать это правильно. Но действительно трудно совершить такой пересмотр, в то время как прогресс, который мы добились, кажется более дешевым в краткосрочной перспективе. На самом деле, не так много людей, которые взламывают GHC, но есть достаточно много унаследованного кода, который можно сохранить. Это одна из причин, почему существует так много дополнительных языков, как DDC, Cayenne, Idris и т. Д. C ++ начинался под маской строгого надмножества C. Добавление новых парадигм всегда требует нового определения языка, или же в конечном итоге возникает сложная путаница. Моя точка зрения во всем этом заключается в том, что для добавления истинно зависимых типов в Haskell потребуется определенное количество потрошения и реструктуризации языка - если мы собираемся сделать это правильно. Но действительно трудно совершить такой пересмотр, в то время как прогресс, который мы добились, кажется более дешевым в краткосрочной перспективе. На самом деле, не так много людей, которые взламывают GHC, но есть достаточно много унаследованного кода, который можно сохранить. Это одна из причин, почему существует так много дополнительных языков, как DDC, Cayenne, Idris и т. Д. C ++ начинался под маской строгого надмножества C. Добавление новых парадигм всегда требует нового определения языка, или же в конечном итоге возникает сложная путаница. Моя точка зрения во всем этом заключается в том, что для добавления истинно зависимых типов в Haskell потребуется определенное количество потрошения и реструктуризации языка - если мы собираемся сделать это правильно. Но действительно трудно совершить такой пересмотр, в то время как прогресс, который мы добились, кажется более дешевым в краткосрочной перспективе. На самом деле, не так много людей, которые взламывают GHC, но есть достаточно много унаследованного кода, который можно сохранить. Это одна из причин, почему существует так много дополнительных языков, как DDC, Cayenne, Idris и т. Д. или заканчивая каким-то сложным беспорядком. Моя точка зрения во всем этом заключается в том, что для добавления истинно зависимых типов в Haskell потребуется определенное количество потрошения и реструктуризации языка - если мы собираемся сделать это правильно. Но действительно трудно совершить такой пересмотр, в то время как прогресс, который мы добились, кажется более дешевым в краткосрочной перспективе. На самом деле, не так много людей, которые взламывают GHC, но есть достаточно много унаследованного кода, который можно сохранить. Это одна из причин, почему существует так много дополнительных языков, как DDC, Cayenne, Idris и т. Д. или заканчивая каким-то сложным беспорядком. Моя точка зрения во всем этом заключается в том, что для добавления истинно зависимых типов в Haskell потребуется определенное количество потрошения и реструктуризации языка - если мы собираемся сделать это правильно. Но действительно трудно совершить такой пересмотр, в то время как прогресс, который мы добились, кажется более дешевым в краткосрочной перспективе. На самом деле, не так много людей, которые взламывают GHC, но есть достаточно много унаследованного кода, который можно сохранить. Это одна из причин, почему существует так много дополнительных языков, как DDC, Cayenne, Idris и т. Д. действительно трудно совершить такой капитальный ремонт, в то время как постепенный прогресс, который мы делаем, кажется более дешевым в краткосрочной перспективе. На самом деле, не так много людей, которые взламывают GHC, но есть достаточно много унаследованного кода, который можно сохранить. Это одна из причин, почему существует так много дополнительных языков, как DDC, Cayenne, Idris и т. Д. действительно трудно совершить такой капитальный ремонт, в то время как постепенный прогресс, который мы делаем, кажется более дешевым в краткосрочной перспективе. На самом деле, не так много людей, которые взламывают GHC, но есть достаточно много унаследованного кода, который можно сохранить. Это одна из причин, почему существует так много дополнительных языков, как DDC, Cayenne, Idris и т. Д.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.