Dependent types in half of D
Jul. 29th, 2015 07:38 pmПосмотрел тут свежее выступление одного из активных пильщиков Идриса: David Christiansen - Coding for Types: The Universe Patern in Idris. Он там рассказывает, что вот в языках с зависимыми типами у нас типы вроде как первоклассные, можно их как обычные значения создавать, передавать и использовать, но вот паттерн-матчить по ним не положено - это отнимет бесплатные теоремы (id может начать возвращать 42 для всех натуральных аргументов, например) и помешает стиранию этих типов из рантайма. Не положено (надо бы сказать "нельзя", но по крайней мере несколько версий назад кое-какой простой матчинг по типам там работал), но порой очень хочется. Для этого есть дизайн паттерн: The Universe Pattern, заключающийся в том, что для интересующего нас набора типов мы делаем тип данных, их кодирующий (аки AST), где хотим матчимся по нему, а когда нужны сами типы, то просто отображаем их из этой кодировки. Например, есть у нас таблички, где поля могут быть строками, целыми или вещественными числами. Описываем это алгебраиком:
и сразу делаем отображение в "настоящие" типы:
Надо превратить поле одного из этих типов в строку? Пожалуйста:
Тут функция с двумя аргументами, где тип второго зависит от значения первого, т.е. зависимые типы как они есть. Можно вызвать printCol TEXT "hi", а можно printCol INTEGER 77, но printCol TEXT 77 уже не скомпилируется, будет ошибка типов.
Похожим образом он описывает схему таблички и отображение ее в тип кортежа, подходящего под эту схему:
Как пример, вот так конкретными значениями описывается схема и данные одной таблицы:
В этой схеме два столбца, interp PeopleSchema нам возвращает HList [String, Integer] - это тип гетерогенного списка из ровно двух элементов, где первый - строка, а второй - целое. Каждый такой список содержит один кортеж таблицы, например, ["David", 1]. Тут мог бы быть обычный тупл, но Дэвид просто задействовал HList, определенный им в том же выступлении несколькими минутами раньше.
Умея по схеме описывать тип данных всей таблицы, можно посчитать сколько всего у людей из нее есть хомячков, а также сделать функцию превращения произвольной таблицы в строку для вывода.
Тут countHamsters работает с таблицами фиксированной схемы, а printRow превращает в строку кортеж из произвольной таблицы со схемой tbl : Table, такой кортеж представлен значением, тип которого получается вычислением interp tbl. Оба аргумента - списки (обычный и гетерогенный), для каждого столбца из одного списка берется значение типа Column, определяющее тип очередного столбца, оно вместе со значением самого столбца передается в printCol. Дальше уже все тривиально.
Ну, зависимыми типами в Идрисе никого не удивишь, а вот задумывались ли вы о том, что зависимые типы давно уже пробрались в мейнстрим и присутствуют в С++ и его младшем и более милом мне собрате D? Но не во всем языке, а в половине - той, что доступна в compile-time. Попробуем пример Дэвида переписать практически один в один на D. В начале у нас был тип данных Column и отображение его в настоящие типы. Пишем:
Дальше была функция printCol с зависимым типом (c : Column) -> interpCol c -> String.
Это уже полноценная функция с двумя аргументами, возвращающая строку. Как и в оригинале, первый аргумент - с типа Column, а вот тип второго аргумента может быть разным, и статическая проверка убеждается, что этот тип это именно interpCol!(c), прямо как в исходнике на Идрисе. Передашь не тот тип - не скомпилируется. Секретный соус тут - двойной набор круглых скобок после printCol: аргументы перечислены в первых, это компайл-тайм аргументы. static if тоже делает свою работу в компайл-тайме.
Теперь научимся описывать схему таблиц, соответствующие данные, и функции, работающие с такими таблицами. Для компайл-тайм списков в D есть родной тип TypeTuple, исходно был нужен для шаблонов с переменным числом параметров. Может содержать самые разные вещи - типы, числа, строки, структуры, шаблоны... Для него в стандартной библиотеке есть готовые списковые операции вроде map/filter и пр. Нам пригодится map, называется staticMap. У TypeTuple есть один нюанс - они автоматически сплющиваются (список из двух списков превращается в один длинный), чтобы этого избежать сделаем обертку:
Компайл-тайм функция listValueTypes будет превращать список значений в список их типов, она пригодится дальше для тайпчекинга. В оригинале на Идрисе столбцы схемы описывались туплами (String, Column), и нужное значение Column извлекалось ф-ей snd. Мы же заведем для такой пары отдельную структуру, где можно извлекать все по имени.
В качестве Table (схемы) будет выступать List, содержащий прозвольное количество таких вот описаний столбцов. Отобразить описание столбца из схемы в настоящий тип поможет ф-я columnType, а interp отобразит список описаний столбцов в список настоящих типов данных, это как в оригинале на Идрисе. Вот так будет выглядеть превращение в строку кортежа произвольной таблицы:
Как и в оригинале, у функции два аргумента: схема tbl и кортеж, имеющий тип, получаемый применением ф-ии interp к tbl. Их соответствие проверяется вычислением listValueTypes!xs и interp!tbl и их сравнением. Внутри мы проходим в цикле по столбцам и аналогично вызываем printCol, передавая описание типа столбца и значение данного столбца. На разных итерациях цикла тип значения x будет разный, заметьте.
Описание конкретной таблицы про хомячков и подсчет их общего количества будет выглядеть так:
Функция countHamsters получает список с кортежами. Тут я использовал встроенный гетерогенный список, и поскольку каждый элемент в нем может быть своего типа, чтобы проверить соответствие данных схеме, проверка типа внесена внутрь цикла. Там для каждого кортежа r вычисляется список типов его элементов и сравнивается с результатом отображения нашей конкретной схемы в типы. Запустим на конретных данных:
Оно компилируется и при запуске выводит:
hamsters: 21, first row: David 10
Полный исходник, с компиляцией и выводом, тут.
Причем данные (в том числе схему) можно было не указывать в исходнике, а поместить в отдельный файл, который распарсить, средства для этого имеются. Поскольку вся интересная работа происходит в компайл-тайме, использовать это можно как в интерпретируемых языках: пишешь "rdmd program.d arguments", программа быстренько компилируется и сразу запускается, отрабатывают сразу обе фазы, на первой из которых, как видно, у нас есть зависимые типы. Такое вот баловство.
data Column = TEXT | REAL | INTEGER
и сразу делаем отображение в "настоящие" типы:
interpCol : Column -> Type interpCol TEXT -> String interpCol REAL -> Double interpCol INTEGER -> Integer
Надо превратить поле одного из этих типов в строку? Пожалуйста:
printCol : (c : Column) -> interpCol c -> String printCol TEXT x = x printCol REAL x = doubleToString x printCol INTEGER x = intToString x
Тут функция с двумя аргументами, где тип второго зависит от значения первого, т.е. зависимые типы как они есть. Можно вызвать printCol TEXT "hi", а можно printCol INTEGER 77, но printCol TEXT 77 уже не скомпилируется, будет ошибка типов.
Похожим образом он описывает схему таблички и отображение ее в тип кортежа, подходящего под эту схему:
Table : Type Table = List (String, Column) interp : Table -> Type interp tbl = HList (map (interpCol . snd) tbl)
Как пример, вот так конкретными значениями описывается схема и данные одной таблицы:
PeopleSchema : Table
PeopleSchema = [("Name", TEXT), ("Hamsters", INTEGER)]
People : List (interp PeopleSchema)
People = [["David", 1], ["Edwin", 0]] В этой схеме два столбца, interp PeopleSchema нам возвращает HList [String, Integer] - это тип гетерогенного списка из ровно двух элементов, где первый - строка, а второй - целое. Каждый такой список содержит один кортеж таблицы, например, ["David", 1]. Тут мог бы быть обычный тупл, но Дэвид просто задействовал HList, определенный им в том же выступлении несколькими минутами раньше.
Умея по схеме описывать тип данных всей таблицы, можно посчитать сколько всего у людей из нее есть хомячков, а также сделать функцию превращения произвольной таблицы в строку для вывода.
countHamsters : List (interp PeopleSchema) -> Integer countHamsters [] = 0 countHamsters ([_, h] :: xs) = h + countHamsters xs printRow : (tbl : Table) -> interp tbl -> String printRow [] [] = "" printRow ((_, t) :: vs) (x :: xs) = printCol t x ++ "\t" ++ printRow vx xs
Тут countHamsters работает с таблицами фиксированной схемы, а printRow превращает в строку кортеж из произвольной таблицы со схемой tbl : Table, такой кортеж представлен значением, тип которого получается вычислением interp tbl. Оба аргумента - списки (обычный и гетерогенный), для каждого столбца из одного списка берется значение типа Column, определяющее тип очередного столбца, оно вместе со значением самого столбца передается в printCol. Дальше уже все тривиально.
Ну, зависимыми типами в Идрисе никого не удивишь, а вот задумывались ли вы о том, что зависимые типы давно уже пробрались в мейнстрим и присутствуют в С++ и его младшем и более милом мне собрате D? Но не во всем языке, а в половине - той, что доступна в compile-time. Попробуем пример Дэвида переписать практически один в один на D. В начале у нас был тип данных Column и отображение его в настоящие типы. Пишем:
enum Column { TEXT, REAL, INTEGER } template interpCol(Column col) { // Column -> Type static if (col==Column.INTEGER) alias interpCol = int; else static if (col==Column.REAL) alias interpCol = double; else static if (col==Column.TEXT) alias interpCol = string; }
Дальше была функция printCol с зависимым типом (c : Column) -> interpCol c -> String.
string printCol(Column c, alias x)() if (is(typeof(x) == interpCol!(c))) { static if (c==Column.TEXT) return x; else return x.to!string; }
Это уже полноценная функция с двумя аргументами, возвращающая строку. Как и в оригинале, первый аргумент - с типа Column, а вот тип второго аргумента может быть разным, и статическая проверка убеждается, что этот тип это именно interpCol!(c), прямо как в исходнике на Идрисе. Передашь не тот тип - не скомпилируется. Секретный соус тут - двойной набор круглых скобок после printCol: аргументы перечислены в первых, это компайл-тайм аргументы. static if тоже делает свою работу в компайл-тайме.
Теперь научимся описывать схему таблиц, соответствующие данные, и функции, работающие с такими таблицами. Для компайл-тайм списков в D есть родной тип TypeTuple, исходно был нужен для шаблонов с переменным числом параметров. Может содержать самые разные вещи - типы, числа, строки, структуры, шаблоны... Для него в стандартной библиотеке есть готовые списковые операции вроде map/filter и пр. Нам пригодится map, называется staticMap. У TypeTuple есть один нюанс - они автоматически сплющиваются (список из двух списков превращается в один длинный), чтобы этого избежать сделаем обертку:
struct List(Things...) { // compile-time HList alias contents = Things; } alias getType(x...) = typeof(x[0]); alias listValueTypes(lst) = List!(staticMap!(getType, lst.contents));
Компайл-тайм функция listValueTypes будет превращать список значений в список их типов, она пригодится дальше для тайпчекинга. В оригинале на Идрисе столбцы схемы описывались туплами (String, Column), и нужное значение Column извлекалось ф-ей snd. Мы же заведем для такой пары отдельную структуру, где можно извлекать все по имени.
struct NC(string A, Column B) { // named column alias name = A; alias col = B; } alias columnType(nc) = interpCol!(nc.col); alias interp(tbl) = List!(staticMap!(columnType, tbl.contents));
В качестве Table (схемы) будет выступать List, содержащий прозвольное количество таких вот описаний столбцов. Отобразить описание столбца из схемы в настоящий тип поможет ф-я columnType, а interp отобразит список описаний столбцов в список настоящих типов данных, это как в оригинале на Идрисе. Вот так будет выглядеть превращение в строку кортежа произвольной таблицы:
string printRow(tbl, xs)() if (is(listValueTypes!xs == interp!tbl)) { string[tbl.contents.length] res; foreach(i, x; xs.contents) res[i] = printCol!(tbl.contents[i].col, x); return res[].join("\t"); }
Как и в оригинале, у функции два аргумента: схема tbl и кортеж, имеющий тип, получаемый применением ф-ии interp к tbl. Их соответствие проверяется вычислением listValueTypes!xs и interp!tbl и их сравнением. Внутри мы проходим в цикле по столбцам и аналогично вызываем printCol, передавая описание типа столбца и значение данного столбца. На разных итерациях цикла тип значения x будет разный, заметьте.
Описание конкретной таблицы про хомячков и подсчет их общего количества будет выглядеть так:
alias PeopleSchema = List!( NC!("Name", Column.TEXT), NC!("Hamsters", Column.INTEGER) ); int countHamsters(rows...)() { int sum = 0; foreach(r; rows) { static assert(is(listValueTypes!r == interp!PeopleSchema)); sum += r.contents[1]; } return sum; }
Функция countHamsters получает список с кортежами. Тут я использовал встроенный гетерогенный список, и поскольку каждый элемент в нем может быть своего типа, чтобы проверить соответствие данных схеме, проверка типа внесена внутрь цикла. Там для каждого кортежа r вычисляется список типов его элементов и сравнивается с результатом отображения нашей конкретной схемы в типы. Запустим на конретных данных:
void main(string[] argv) { alias People = TypeTuple!( List!("David", 10), List!("Edwin", 11) ); int n = countHamsters!People; string s = printRow!(PeopleSchema, People[0]); writeln("hamsters: ", n, ", first row: ", s); }
Оно компилируется и при запуске выводит:
hamsters: 21, first row: David 10
Полный исходник, с компиляцией и выводом, тут.
Причем данные (в том числе схему) можно было не указывать в исходнике, а поместить в отдельный файл, который распарсить, средства для этого имеются. Поскольку вся интересная работа происходит в компайл-тайме, использовать это можно как в интерпретируемых языках: пишешь "rdmd program.d arguments", программа быстренько компилируется и сразу запускается, отрабатывают сразу обе фазы, на первой из которых, как видно, у нас есть зависимые типы. Такое вот баловство.
no subject
Date: 2015-07-29 02:20 pm (UTC)Так если ограничиться compile-time, то это не зависимые типы, а всего лишь операторы над типами (lambda-w, а не lambda-P). Это тогда надо говорить, что lambda-w - "зависимые типы, но только в компайлтайме"?
no subject
Date: 2015-07-29 02:43 pm (UTC)Вот есть функция
f : (b : Bool) -> if b then Int else String
f True = 1
f False = "no"
Завтип? Определенно.
Вот она же в том подмножестве D:
auto f(bool b)() { static if (b) return 1; else return "no"; }Мы ее можем вызывать из вполне полноценного кода, просто исполняется он при вызове "интерпретатора" - который у компилятора внутри. От того, что значение b известно на этой фазе, оно типом не становится.
см. http://dpaste.dzfl.pl/81b1e157ac3b
in half of Python
Date: 2015-07-30 07:59 am (UTC)def f(s): if (s == "int"): return 42 elif (s == "str"): return "Hello world!"Единственная разница лишь в том когда именно выполняется код в runtime или compile time. Но это не существенно, так как относится к программе в целом, а функция f в обоих случаях... выполняется, то есть для нее это определенно runtime.
[Recall]
Date: 2015-07-30 08:23 am (UTC)Re: in half of Python
Date: 2015-07-30 10:16 am (UTC)Еще совершенно правильный вопрос - не становится ли D на фазе интерпретации динамически типизированным языком, где как продемонстрированно, можно изображать что-то похожее на завтипы, но без статических проверок. И тут ответ неоднозначный - там получается смесь из статических и динамических типов, например в printCol первым параметром строку вместо Column не передашь, компилятор ругнется и внутрь туда заходить не станет, т.е. некоторые проверки "более статические чем другие". :)
no subject
Date: 2015-07-30 10:30 am (UTC)Вообще, сама фраза "у нас есть зависимые типы, но только в части языка из компайлтайма" в точности соответствует какой-нибудь фразе вроде "в нашем чистом языке есть функции, но только нульарные". Но вы ж так не говорите? Потому что это глупость несусветная, очевидный бред. Именно возможность применения ф-и к аргументам, это то особенное, что и делает функцию функцией. Точно так же именно тот факт что типы зависят от _неизвестного наперед во время компиляции терма_ (то есть зависят от информации, узнать которую нельзя до запуска программы на конкретных аргументах) и делает зависимые типы зависимыми.
> Завтип? Определенно.
Нет, это не завтип.
Давайте сначала определимся - любой функциональный тип, формально, есть частный случай зависимого типа (так же как константа есть частный случай функции), но нам же не это интересно? Нас интересуют ровно те типы, которые можно выразить в системах с зависимыми типами, но нельзя выразить в системах без зависимых типов. Ваша функция тривиально выражается без зависимых типов - вы просто вводите тип True, тип False, сорт Bool и пишите без каких-либо проблем свою f (фактически, выполняете обратное к изложенному в начале вашего поста преобразование).
> От того, что значение b известно на этой фазе, оно типом не становится.
Вот в этом и есть ошибка. Нет способа отличить известное на стадии компиляции значение от типа, если ваша система типов достаточно сильна, чтобы уметь это значение закодировать тем или иным способом (а она в общем практически всегда достаточно сильна).
no subject
Date: 2015-07-30 11:15 am (UTC)В теории типов нет вообще упоминания разделения на компайл-тайм и ран-тайм. Кстати, в примерах на Идрисе тут все данные тоже известны при компиляции, прямо в исходнике записаны. Что теперь, Идрис не завтипизированный язык?
Все очень просто™: тип - это то, что может участвовать справа в выражении "терм : тип". В языке D термы true и false типами не являются, и не важно, можно ли описать типы с похожими именами.
>Нет, это не завтип.
Жаль, авторы Идриса, Агды и все остальные их коллеги сильно растроятся. Да еще и столько учебников теперь переписать придется.
>Нет способа отличить известное на стадии компиляции значение от типа
Во-первых, есть - см. определение типа. Во-вторых, для приведенного кода (интепретируемая часть D) это не стадия компиляции, это рантайм, как тут рядом уже выяснили.
no subject
Date: 2015-07-30 11:20 am (UTC)Re: in half of Python
Date: 2015-07-30 11:22 am (UTC)Хочу также уточнить последнюю фразу:
Во время компиляции в разные моменты времени идет поочередно компиляция и выполнения различных функций. То есть есть некая очередность, и во время компиляции некоторой единицы кода возможна компиляция и выполнение некоторых других единиц. (поищите "Понимание constexpr" тут (http://habrahabr.ru/post/142352/), это конечно С++ но идеи та же).
А если еще и выбросить компиляцию отдельных функций во время компиляции всего проекта (что в принципе не должно изменить конечный результат, так как ошибки их компиляции никуда не денутся, а превратятся в ошибки их выполнения), то как раз получим полностью динамический язык.
no subject
Date: 2015-07-30 11:57 am (UTC)Верно.
> Кстати, в примерах на Идрисе тут все данные тоже известны при компиляции, прямо в исходнике записаны.
Это, конечно, не так, иначе на идрисе нельзя было бы написать факториал.
> "терм : тип"
В таком определении все системы лямбда-куба стали эквивалентными. Думаю, в этом с вами никто не согласится.
> Жаль, авторы Идриса, Агды и все остальные их коллеги сильно растроятся. Да еще и столько учебников теперь переписать придется.
Еще раз - абсолютно любой тип является зависимым. Но вы же не утверждаете, что во всех языках со статической типизацией есть зависимые типы?
Да, операторы над типами (то есть вычисления на уровне типов, то, что мы видим в D) можно выразить через зависимые типы, по-этому они являются "кусочком зависимых типов", но специально для этого кусочка придумали специальный термин.
В точности точно так же полиморфные типы являются кусочком зависимых типов, но для них придумали (точно также) специальное название, и мы пользуемся этим специальным названием. Почему вы не говорите, что "в любом языке с параметрическим полиморфизмом либо перегрузкой функций есть кусочек зависимых типов"? Ведь это формально верное утверждение (в точности аналогичное вашему утверждению про зависимые типы в D).
В D вы имеете сахарок, который позволяет вам писать вместо Zero и Succ (Succ (Succ Zero)) 0 и 3, но синтаксический сахар на то и синтаксический сахар, что выразительности системы он не меняет.
Так что называть зависимыми типами операторы над типами (а то? что в D - это именно операторы над типами, согласно определению данной системы, эта абстракция была введена именно за тем, чтобы было как называть систему типов, выразительно эквивалентную тому, что мы видим в D) - это нечто среднее между некорректным утвержеднием и заведомой ложью (хоть и во благо, я понимаю - вы просто пытаетесь показать какой у вас крутой язык, что вот типа там зависимые типы даже есть, эдакий рокетсаенс).
По факту в описанной в вашем посте ситуации важно то, что, на деле, много вещеЙ, для которых используются зависимые типы, легко делаются и в менее выразительных системах, без зависимых типов. Вот, например, в D зависимых типов нет, но многие вещи, что делают на зависимых типах в том же Идрисе, можно, несмотря на это, сделать в D. Более того, в D есть синтаксический сахар, который позволяет эффективно делать эти вещи не только упоротым мудакам, но и нормальным людям. По-этому D - молодец. Как-то так.
> Во-первых, есть - см. определение типа.
Ну вот вам "1" - это тип или терм? Отличите, пожалуйста.
no subject
Date: 2015-07-30 12:01 pm (UTC)no subject
Date: 2015-07-30 01:05 pm (UTC)> Вот есть функция
В идрисе это, действительно, функция. А в D - нет, потому что функцию можно применять к неконстантам, а f - нельзя.
no subject
Date: 2015-07-30 03:14 pm (UTC)Это, конечно, не так, иначе на идрисе нельзя было бы написать факториал.
Еще раз: в приведенных тут примерах все данные в исходниках.
>> "терм : тип"
В таком определении все системы лямбда-куба стали эквивалентными.
Нет, не стали. Откройте учебник.
>Но вы же не утверждаете, что во всех языках со статической типизацией есть зависимые типы?
Не утверждаю, верно.
>Ну вот вам "1" - это тип или терм? Отличите, пожалуйста.
В упомянутых двух языках - терм.
no subject
Date: 2015-07-30 03:16 pm (UTC)no subject
Date: 2015-07-30 03:19 pm (UTC)no subject
Date: 2015-07-30 03:57 pm (UTC)Допустим, вам надо провести какую-то исследовательскую работу для языка D, соответственно, вы пытаетесь формализовать его систему типов. Логично, что на основе каких-то уже имеющихся формализмов. У вас есть два варианта:
1. Вы просто предполагаете, что значения в нужных контекстах лифтятся в соответствующие типы, в результате чего все формально строго и без всяких проблем ложится на вариант SystemF.
2. Вы попытаетесь натянуть сову на глобус, выдумаете "зависимые типы времени компиляции", введете функции, которые не являются функциями и прочий бред.
Что вы выберете вот в такой вот ситуации? Я уверен, что первый вариант. Почему же в посте вы скатываетесь по маркетинговым причинам к откровенной лжи?
no subject
Date: 2015-07-30 04:01 pm (UTC)Мне лестно, что вы считаете меня автором лямбда-исчисления, но его придумал не я. Если t1 и t2 термы, то (t1 t2) - терм. Правило аппликации. У вас оно нарушено.
> Во-вторых, никто не мешает положить значение в файл и прочитать при запуске (rdmd program.d file.txt). Это будет константа или не константа? Функция или не функция?
Вы вообще осознаете сейчас уровень бреда на который скатываетесь, пытаясь натянуть зависимые типы на две противоположные вершины лямбда-куба? Какие файлы и чтения при запуске? Мы о синтаксисе и семантике языка говорим.
no subject
Date: 2015-07-30 04:06 pm (UTC)Еще раз - нету никакой такой "компиляции", когда мы говорим о системе типов. Вы это сами же выше верно указали, а теперь уже забыли, когда утверждение стало неудобным?
> это "константы", а потому по-вашему он не может быть зависимотипизированным.
Вы правда не понимаете о чем речь или это такой троллинг?
lambda x. x+1, 1 - это константа (терм, который не содержит свободных переменных), х - нет. Какое у нее известное на этапе компиляции (пусть даже эта ваша "компиляция" идет во время "интерпретации") значение? Если f - функция, то мы можем написать валидный терм lambda x. (f x)+1, с вашей f он не пишется, по-этому ваша f - не функция.
no subject
Date: 2015-07-30 04:15 pm (UTC)Предполагать про лифтинг в SystemF - это и есть натягивание совы. Есть 1 и есть int, первое есть терм, второе тип. Есть интепретатор и есть компилятор, они оба сходятся, что 1 это терм. Но один из них умеет этот терм 1 отобразить в тип для
второгообоих, например char[1]. Я рассматриваю не систему типов просто D (там завтипов нет), а систему типов интерпретируемого во время компиляции подмножества, это несколько отличающийся язык.no subject
Date: 2015-07-30 04:21 pm (UTC)Давайте по пунктам. Почему мою f нельзя применить к неконстантам?
no subject
Date: 2015-07-30 04:39 pm (UTC)не-не, начали вы с "зависимыми типами в половине, доступной при компиляции", потом я подхватил а потом уже и вы. Значит, остановимся давайте на "вычисления на уровне типов", согласны?
> Давайте по пунктам. Почему мою f нельзя применить к неконстантам?
Потому что такая семантика у D. Ошибку выдает. Вычислений на уровне типов. Я специально проверил.
ЗЫ: на самом деле, кстати, мог бы и не выдавать, выразительных возможностей языка и системы типов это бы никак не изменило. Но раз уж выдает - грех на это не сослаться.
no subject
Date: 2015-07-30 04:54 pm (UTC)С этим, конечно, никто не спорит. Речь идет о том что ваша "1" в соответствующих контекстах ведет себя в точности как тип, конечно не Int, а какой-нибудь Succ Zero.
То есть, понимаете, есть лямбда-исчисление, в нем нету списков или там чисел с булами, но они моделируются на лямбдах. В практически применимых языках у нас и инты и буллы и списки со всем остальным есть, для удобства, но при этом системы эквивалентны.
Потом мы берем и вводим лямбда-исчисление на уровне типов. И тут есть два варианта - либо оставить лямбду (как в хаскеле), и тогда для того чтобы написать что-либо нетривиальное надо быть мудаком, либо добавить туда все те же булы, инты, списки да и вообще всю копию нашего языка, на котором эта система типов хостится. В этом случае мы можем писать нетривиальные вещи и не быть мудаками - вот так вот сделали в D. Но так же как лямбда без интов эквивалентна лямбде с интами так и тут. Это эквивалентные системы, просто не надо кодировать типы руками.
> а систему типов интерпретируемого во время компиляции подмножества, это несколько отличающийся язык.
Ага, понял. Но все равно не сходится - с точки зрения этого подмножества integer или bool - это значения, с-но на уровне типов там обычная типизированная лямбда.
no subject
Date: 2015-07-30 04:57 pm (UTC)1. Речь не о системе типов всего D, а о системе типов подъязыка, доступного в CTFE. Именно поэтому я исходно начал про "при компиляции" - чтобы указать, о каком языке я вообще говорю.
2. В полноценном завтипизированном языке lambda x. (f x)+1 не пройдет проверку, т.к. f false возвращает строку. Давайте это исправим и возжелаем
lambda x. (f x, f x)
нет возражений?
Лямбд в указанном языке кажется нету, но ведь именованная функция тоже подойдет?
Пишем, работает:
http://dpaste.dzfl.pl/a35a7144df67
no subject
Date: 2015-07-30 05:13 pm (UTC)Ура!
Там int и bool это и значения, и типы (т.к. использутся для "int sum = ..."). Ровно как и в Идрисе и подобных - там же тоже синтаксически только термы на самом деле, и терм : терм.
А вот что там (в CTFE подмножестве D) на уровне типов по-хорошему - вопрос мутный. Условный Идрис это или условный Питон - вот в чем вопрос.
no subject
Date: 2015-07-30 05:23 pm (UTC)> lambda x. (f x, f x)
> нет возражений?
Да, конечно.
> Пишем, работает:
Нет, не работает, вы же написали хрень-функцию и применили ее к результату хрень-функции. Хрень-функции композятся, это бесспорно. А вот функции с хрень-функциями - нет. Да, собственно, там даже компилятор намекает - ваши хрень-функции это не функции, а темплейты, то есть полиморфные функции, которые получают полиморфный аргумент (тип, то есть) и там уже какой-то терм возвращают.
Но это вообще не суть, вы указали что речь не о D в целом, а конкретно о доступном во время компиляции подмножестве, и что там зависимые типы и такое все. Я в ответ вот, что сделаю, следите за руками, я беру обычный динамический лисп. Он динамический, везде динамический - и в рантайме и в компайлтайме, везде. Ну лисп, короче. И пишу вот такой вот код:
http://pastebin.com/DgvEYg8i
и он работает в точности как ваша f (вообще не совсем, подставлять надо именно значения #t/#f, переделать на любые constexpr на самом деле тривиально, просто не хочу загружать лишними особенностями работы макросистемы)
и получается как бы что у меня в компайлтайме зависимые типы? хотя язык компиляции полностью динамический
сразу наперед - вы можете заметить что раз основной язык динамический, то тут как бы зависимых типов и нет, потому что тип f на самом деле boolean->Any, а точнее и вовсе Any, на что легко возразить - достаточно заменить рантайм-язык простой типизированной лямбдой и оставить компайлтайм-язык без изменений, чтобы поправить эту ситуацию:
http://pastebin.com/XpdkN951
вот тот тип что указан после двоеточия - это именно статический тип, определяется во время компиляции. И вот выходит что у f зависимый тип и репл у меня тоже с зависимыми типами, прям как в идрисе, я все верно понимаю?
no subject
Date: 2015-07-30 05:56 pm (UTC)Ну так да, у вас же на уровне типов идет подмножество исходного языка, с-но, если в исходном типы есть - то и в этом будут. Получается, что на уровне типов мы имеем типы-значения и типы-типы (сорта), причем они совпадают в названиях, но не смешиваются, то есть это разные сущности. Смотрите ниже пример с лиспом, там динамический язык в компайлтайме по вашей логике зависимые типы имеет. Я при желании могу и в точности аналогичный D пример написать, лифтанув в 1-фазу ("компайлтайм" в терминологии Racket) типизированный диалект из 0-фазы, правда через костыль. Причем понятно же, что это тот же самый язык, что в 0-фазе, а раз он тот же самый - то и система типов та же самая, то есть простая типизированная лямбда. И в D таже логика, от того что вы язык "в компайлтайм лифтанули" он не стал другим языком (более выразительным).
no subject
Date: 2015-07-30 06:11 pm (UTC)Ну вот мы и пришли к правильным вопросам, тут рядом уже обсуждавшимся.
no subject
Date: 2015-07-31 06:06 am (UTC)http://thedeemon.livejournal.com/102275.html?thread=1754499#t1754499
Здесь-то все просто, смотрите:
1. язык А есть подмножество языка В
2. если подмножество содержит фичу С, то надмножество содержит фичу С
3. язык А содержит зависимые типы
=> В содержит фичу С
значит, если на подмножестве D, доступном на уровне типов, есть зависимые типы, то они и в самом D есть
Вы согласны, как я понимаю, что это не так - значит неверна одна из трех посылок.
При этом, не противореча вашей логике, можно считать, что А = В (для D это не верно, вообще говоря, но можно сделать в Racket том же), тогда 1 и 2 становятся тавтологиями и единственным неверным утверждением может быть только 3.
Еще один момент. Допустим, у f действительно зависимый тип, допустим, это тип Х, как мне написать функцию g, которая будет принимать (или возвращать) функции типа Х и только их? Никак, потому что не получится записать этот Х, а значит его и нету.
Сделайте мне так:
Date: 2015-07-31 07:48 am (UTC)no subject
Date: 2015-07-31 03:09 pm (UTC)no subject
Date: 2015-07-31 06:39 pm (UTC)no subject
Date: 2015-07-31 06:44 pm (UTC)http://stuff.thedeemon.com/lj/susp_cmnt.png
А без ссылки и наличия не видно.
На остальное позже отвечу. Тут уже первый пункт сомнителен - считать подмножеством или это разные языки просто.
Re: Сделайте мне так:
Date: 2015-07-31 06:57 pm (UTC)void main(string[] argv) { enum x = import("argv.txt").length > 42; writeln( lam!x ); }Аргументы коду на обсуждаемой части языка передаются либо через файлы, либо через ключи компиляции.
no subject
Date: 2015-08-01 09:49 am (UTC)no subject
Date: 2015-08-01 04:38 pm (UTC)no subject
Date: 2015-08-05 07:43 am (UTC)#lang typed/racket (require (for-syntax racket racket/syntax)) (begin-for-syntax (define/contract (f x) (->i ([x boolean?]) [result (x) (if x integer? string?)]) (match x [#t 1] [#f "no"])) (define (f-proxy stx) (syntax-case stx () [(_ arg) #`#,(f (syntax-local-eval #'arg))]))) (define-syntax f f-proxy) (: x Integer) (define x (f (< 1 5))) (: y String) (define y (f (> 1 5)))здесь тайпчек, как и должно быть, в компайлтайме, аргументы f вычисляются в компайлтайме, выражение (f ...) в итоге тайпчекером рассматривается либо как имеющее тип Integer, либо как String (в зависимости от аргумента), если типы х и y местами поменять - тайпчекер даст ошибку типов, если выражение окажется не boolean? - contract blame, усли нарушится указанная в [result (x) (if x integer? string?)] семантика - contract blame, ессно это все в компайлтайме, при применении f к аргументу.
Вопрос: это получились зависимые типы? Но я-то точно знаю, что там динамика :)
no subject
Date: 2015-08-06 11:08 am (UTC)Но один вопрос: почему проверяемое при компиляции "(: x Integer)" это динамика, а "x : Integer" в другом языке - статика?
no subject
Date: 2015-08-06 11:54 am (UTC)На эти куски:
(define (f-proxy stx)
(syntax-case stx ()
[(_ arg) #`#,(f (syntax-local-eval #'arg))]))
и:
(define-syntax f f-proxy)
можете не обращать внимания, это просто обертка, чтобы выглядело как в ваших примерах, (begin-for-syntax exprs) - значит, exprs будут выполнены во время компиляции (на самом деле в +1 фазе относительно visite в enclosed module, а не при компиляции, но для нашего обсуждения эта разница в семантике некритична)
(define/contract (f args ...) contract exprs ...) - определяет функцию (lambda (args ...) exprs ...) с именем f и контрактом (есс-но проверяющимся при вызове ф-и, то есть для обычной (не в begin-for-syntax) функции это рантайм) contract. Остальное, полагаю, и так ясно.
> Но один вопрос: почему проверяемое при компиляции "(: x Integer)" это динамика, а "x : Integer" в другом языке - статика?
Не совсем понятен вопрос. (: x Integer) проверяется в компайлтайме тайпчекером, с-но _на уровне компайлтайме_ у нас динамика, если тайпчекер написан на динамике, и статика, если он написан на статике. В случае Racket тайпчекер - просто одна из библиотек, и он написаном на самом Racket (который динамический) и на том же Racket написан макрос f. Вопрос таокго, что такое (: x Integer) в D мне кажется совершенно неинтересным - ведь в D можно написать тайпчекер на чем угодно. Интерес представляет, на каком языке написана f (то самое "доступное при компиляции подмножество"). В Racket - это Racket (вот прям совсем в точности он же, на все 100%, по-этому вопросы типа "а является ли оно подмножеством или не подмножеством или еще что...." нас не волнуют), и этот язык совершенно точно динамический.
no subject
Date: 2015-08-06 12:17 pm (UTC)no subject
Date: 2015-08-07 07:21 am (UTC)Он написан на "подмножестве языка, доступном при компиляции". Так что если мы обсуждаем "подмножество языка, доступное при компиляции", то мы обсуждаем язык, на котором написан тайпчекер.
> Разница между динамическими и статическими типами отличается от разницы между динамической и статической проверкой типов
Чем?
no subject
Date: 2015-09-09 09:16 pm (UTC)Программа должна вводить из консоли сначала число n, а затем два списка длины n из натуральных чисел, и искать их попарную сумму. Если мы захотим для подобного zip взять тип в духе
zip : {A, B, C : Set}{n : N} -> (A -> B -> C) -> List n A -> List n B -> List n C
то в завтипизированном языке мы сможем сначала построить доказательство того, что мы из консоли ввели ровно n чисел в каждом списке (потому что в противном случае мы вместо вычисления суммы писали юзеру диагностик какой-нибудь), а затем, используя эти пруфы, вызвать такую zip с заранее неизвестным n.
В D же ничего подобного построить нельзя, потому что это просто система с иерархией юниверсов, где 1 имеет тип Set n для некоторого ограниченного сверху числа n. Наличие иерархии юниверсов ещё не делает язык завтипизированным. У подобной системы, впрочем, есть определённая прелесть, например, я хорошо представляю как её компилировать (Set 0 в рантайм, остальное в компайлтайм), в отличие от завтипизированных языков, где для этого в Coq есть разделение на Set и Prop, а в Agda, если я правильно понял, proof irrelevance.
no subject
Date: 2015-09-10 04:06 am (UTC)Я нигде и не говорил, что в D есть зависимые типы.
no subject
Date: 2015-09-10 04:09 am (UTC)