thedeemon: (office)
[personal profile] thedeemon
Посмотрел тут свежее выступление одного из активных пильщиков Идриса: David Christiansen - Coding for Types: The Universe Patern in Idris. Он там рассказывает, что вот в языках с зависимыми типами у нас типы вроде как первоклассные, можно их как обычные значения создавать, передавать и использовать, но вот паттерн-матчить по ним не положено - это отнимет бесплатные теоремы (id может начать возвращать 42 для всех натуральных аргументов, например) и помешает стиранию этих типов из рантайма. Не положено (надо бы сказать "нельзя", но по крайней мере несколько версий назад кое-какой простой матчинг по типам там работал), но порой очень хочется. Для этого есть дизайн паттерн: The Universe Pattern, заключающийся в том, что для интересующего нас набора типов мы делаем тип данных, их кодирующий (аки AST), где хотим матчимся по нему, а когда нужны сами типы, то просто отображаем их из этой кодировки. Например, есть у нас таблички, где поля могут быть строками, целыми или вещественными числами. Описываем это алгебраиком:
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", программа быстренько компилируется и сразу запускается, отрабатывают сразу обе фазы, на первой из которых, как видно, у нас есть зависимые типы. Такое вот баловство.

Date: 2015-07-29 02:20 pm (UTC)
From: [identity profile] valentin budaev (from livejournal.com)
> Ну, зависимыми типами в Идрисе никого не удивишь, а вот задумывались ли вы о том, что зависимые типы давно уже пробрались в мейнстрим и присутствуют в С++ и его младшем и более милом мне собрате D? Но не во всем языке, а в половине - той, что доступна в compile-time.

Так если ограничиться compile-time, то это не зависимые типы, а всего лишь операторы над типами (lambda-w, а не lambda-P). Это тогда надо говорить, что lambda-w - "зависимые типы, но только в компайлтайме"?
Edited Date: 2015-07-29 02:21 pm (UTC)

Date: 2015-07-29 02:43 pm (UTC)
From: [identity profile] thedeemon.livejournal.com
Это почему же?
Вот есть функция
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)
From: [identity profile] dima-starosud.livejournal.com

    def f(s):
        if (s == "int"):
            return 42
        elif (s == "str"):
            return "Hello world!"

Единственная разница лишь в том когда именно выполняется код в runtime или compile time. Но это не существенно, так как относится к программе в целом, а функция f в обоих случаях... выполняется, то есть для нее это определенно runtime.
Edited Date: 2015-07-30 08:00 am (UTC)

[Recall]

Date: 2015-07-30 08:23 am (UTC)
From: [identity profile] dima-starosud.livejournal.com
"Half of dependent types in D".

Re: in half of Python

Date: 2015-07-30 10:16 am (UTC)
From: [identity profile] thedeemon.livejournal.com
Да, для интерпретируемой части Ди это "рантайм", т.е. там типы умеют зависеть от "рантайм"-значений. Причем исполняется там функция вообще странным образом: ошибка внутри ф-ии не прерывает такое исполнение сразу, они накапливаются и выводятся. Но если были ошибки, то до генерации бинарного кода и запуска второй стадии - настоящего рантайма - дело не дойдет, и поэтому даже если какие-то функции отработали ошибочно, зря, результат их все равно не будет иметь эффекта (записать файл, например, мы на фазе интерпретации не можем, все эффекты на второй стадии). На первой фазе код чистый, его не грех и повыполнять прямо в процессе тайпчекинга.

Еще совершенно правильный вопрос - не становится ли D на фазе интерпретации динамически типизированным языком, где как продемонстрированно, можно изображать что-то похожее на завтипы, но без статических проверок. И тут ответ неоднозначный - там получается смесь из статических и динамических типов, например в printCol первым параметром строку вместо Column не передашь, компилятор ругнется и внутрь туда заходить не станет, т.е. некоторые проверки "более статические чем другие". :)

Date: 2015-07-30 10:30 am (UTC)
From: [identity profile] valentin budaev (from livejournal.com)
Все очень просто. Зависимые типы - отображение из термов в типы. Операторы над типами - отображение из типов в типы. Как только вы ограничиваете термы известными на момент компиляции, как в первом случае, вы сразу даете возможность закодировать ваш терм типом (раз этот терм известен на момент компиляции), по-этому все подобные отношения терм->тип переписываются в тип->тип. Исключений нет.

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

> Завтип? Определенно.

Нет, это не завтип.

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

> От того, что значение b известно на этой фазе, оно типом не становится.

Вот в этом и есть ошибка. Нет способа отличить известное на стадии компиляции значение от типа, если ваша система типов достаточно сильна, чтобы уметь это значение закодировать тем или иным способом (а она в общем практически всегда достаточно сильна).
Edited Date: 2015-07-30 10:35 am (UTC)

Date: 2015-07-30 11:15 am (UTC)
From: [identity profile] thedeemon.livejournal.com
Это какие-то удивительные фантазии, окститесь.
В теории типов нет вообще упоминания разделения на компайл-тайм и ран-тайм. Кстати, в примерах на Идрисе тут все данные тоже известны при компиляции, прямо в исходнике записаны. Что теперь, Идрис не завтипизированный язык?
Все очень просто™: тип - это то, что может участвовать справа в выражении "терм : тип". В языке D термы true и false типами не являются, и не важно, можно ли описать типы с похожими именами.

>Нет, это не завтип.
Жаль, авторы Идриса, Агды и все остальные их коллеги сильно растроятся. Да еще и столько учебников теперь переписать придется.

>Нет способа отличить известное на стадии компиляции значение от типа

Во-первых, есть - см. определение типа. Во-вторых, для приведенного кода (интепретируемая часть D) это не стадия компиляции, это рантайм, как тут рядом уже выяснили.

Date: 2015-07-30 11:20 am (UTC)
From: [identity profile] thedeemon.livejournal.com
Тут хороший вопрос - может ли по-вашему существовать интерпретируемый язык с зависимыми типами (например, REPL Идриса).

Re: in half of Python

Date: 2015-07-30 11:22 am (UTC)
From: [identity profile] dima-starosud.livejournal.com
На самом деле именно динамическим (я тут (http://starosud.blogspot.com/2013/10/dependent-types-dont-need-proofs.html) писал что-то очень похожее).

Хочу также уточнить последнюю фразу:
некоторые проверки "более статические чем другие"

Во время компиляции в разные моменты времени идет поочередно компиляция и выполнения различных функций. То есть есть некая очередность, и во время компиляции некоторой единицы кода возможна компиляция и выполнение некоторых других единиц. (поищите "Понимание constexpr" тут (http://habrahabr.ru/post/142352/), это конечно С++ но идеи та же).

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

Date: 2015-07-30 11:57 am (UTC)
From: [identity profile] valentin budaev (from livejournal.com)
> В теории типов нет вообще упоминания разделения на компайл-тайм и ран-тайм.

Верно.

> Кстати, в примерах на Идрисе тут все данные тоже известны при компиляции, прямо в исходнике записаны.

Это, конечно, не так, иначе на идрисе нельзя было бы написать факториал.

> "терм : тип"

В таком определении все системы лямбда-куба стали эквивалентными. Думаю, в этом с вами никто не согласится.

> Жаль, авторы Идриса, Агды и все остальные их коллеги сильно растроятся. Да еще и столько учебников теперь переписать придется.

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

Да, операторы над типами (то есть вычисления на уровне типов, то, что мы видим в D) можно выразить через зависимые типы, по-этому они являются "кусочком зависимых типов", но специально для этого кусочка придумали специальный термин.

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

В D вы имеете сахарок, который позволяет вам писать вместо Zero и Succ (Succ (Succ Zero)) 0 и 3, но синтаксический сахар на то и синтаксический сахар, что выразительности системы он не меняет.

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

По факту в описанной в вашем посте ситуации важно то, что, на деле, много вещеЙ, для которых используются зависимые типы, легко делаются и в менее выразительных системах, без зависимых типов. Вот, например, в D зависимых типов нет, но многие вещи, что делают на зависимых типах в том же Идрисе, можно, несмотря на это, сделать в D. Более того, в D есть синтаксический сахар, который позволяет эффективно делать эти вещи не только упоротым мудакам, но и нормальным людям. По-этому D - молодец. Как-то так.

> Во-первых, есть - см. определение типа.

Ну вот вам "1" - это тип или терм? Отличите, пожалуйста.
Edited Date: 2015-07-30 12:03 pm (UTC)

Date: 2015-07-30 12:01 pm (UTC)
From: [identity profile] valentin budaev (from livejournal.com)
А в чем проблема? Как вы выше отметили, формально-то говоря, понятия интерпретации/компиляции и рантайма вообще типизации строго ортогональны. Правильно говорить про "вычисления на уровне типов" и "вычисления на уровне термов" или что-то вроде этого, а _когда_ эти вычисления происходят - ну это не важно, вобщем-то, никто не мешает нам тайпчек производить непосредственно во время выполнения программы (имеется ввиду именно алгоритмически тайпчек для статической системы типов, а не как в динамике). То есть можно это реализовать как-то так, что к термам привязываются определенные санки, которые содержат в себе вычисления для тайпчека данного терма и откладываются до тех пор, пока терм не начнет исполняться.
Edited Date: 2015-07-30 12:05 pm (UTC)

Date: 2015-07-30 01:05 pm (UTC)
From: [identity profile] valentin budaev (from livejournal.com)
Да, ну и совсем простое доказательство, что там в вашей f не зависимый тип (формально корректное, но не поясняющее сути). Все очень просто:

> Вот есть функция

В идрисе это, действительно, функция. А в D - нет, потому что функцию можно применять к неконстантам, а f - нельзя.

Date: 2015-07-30 03:14 pm (UTC)
From: [identity profile] thedeemon.livejournal.com
>> Кстати, в примерах на Идрисе тут все данные тоже известны при компиляции, прямо в исходнике записаны.

Это, конечно, не так, иначе на идрисе нельзя было бы написать факториал.


Еще раз: в приведенных тут примерах все данные в исходниках.

>> "терм : тип"
В таком определении все системы лямбда-куба стали эквивалентными.


Нет, не стали. Откройте учебник.

>Но вы же не утверждаете, что во всех языках со статической типизацией есть зависимые типы?

Не утверждаю, верно.

>Ну вот вам "1" - это тип или терм? Отличите, пожалуйста.

В упомянутых двух языках - терм.

Date: 2015-07-30 03:16 pm (UTC)
From: [identity profile] thedeemon.livejournal.com
Проблема в ваших определениях: у интерпретируемого языка все данные известны "при компиляции", это "константы", а потому по-вашему он не может быть зависимотипизированным.

Date: 2015-07-30 03:19 pm (UTC)
From: [identity profile] thedeemon.livejournal.com
Во-первых, это очередное определение, которое вы нафантазировали. Во-вторых, никто не мешает положить значение в файл и прочитать при запуске (rdmd program.d file.txt). Это будет константа или не константа? Функция или не функция?

Date: 2015-07-30 03:57 pm (UTC)
From: [identity profile] valentin budaev (from livejournal.com)
Ну нет, так не пойдет, вы просто игнорируете общеизвестные определения, зайдем с другой стороны.

Допустим, вам надо провести какую-то исследовательскую работу для языка D, соответственно, вы пытаетесь формализовать его систему типов. Логично, что на основе каких-то уже имеющихся формализмов. У вас есть два варианта:
1. Вы просто предполагаете, что значения в нужных контекстах лифтятся в соответствующие типы, в результате чего все формально строго и без всяких проблем ложится на вариант SystemF.
2. Вы попытаетесь натянуть сову на глобус, выдумаете "зависимые типы времени компиляции", введете функции, которые не являются функциями и прочий бред.

Что вы выберете вот в такой вот ситуации? Я уверен, что первый вариант. Почему же в посте вы скатываетесь по маркетинговым причинам к откровенной лжи?
Edited Date: 2015-07-30 04:10 pm (UTC)

Date: 2015-07-30 04:01 pm (UTC)
From: [identity profile] valentin budaev (from livejournal.com)
> Во-первых, это очередное определение, которое вы нафантазировали.

Мне лестно, что вы считаете меня автором лямбда-исчисления, но его придумал не я. Если t1 и t2 термы, то (t1 t2) - терм. Правило аппликации. У вас оно нарушено.

> Во-вторых, никто не мешает положить значение в файл и прочитать при запуске (rdmd program.d file.txt). Это будет константа или не константа? Функция или не функция?

Вы вообще осознаете сейчас уровень бреда на который скатываетесь, пытаясь натянуть зависимые типы на две противоположные вершины лямбда-куба? Какие файлы и чтения при запуске? Мы о синтаксисе и семантике языка говорим.

Date: 2015-07-30 04:06 pm (UTC)
From: [identity profile] valentin budaev (from livejournal.com)
> Проблема в ваших определениях: у интерпретируемого языка все данные известны "при компиляции"

Еще раз - нету никакой такой "компиляции", когда мы говорим о системе типов. Вы это сами же выше верно указали, а теперь уже забыли, когда утверждение стало неудобным?

> это "константы", а потому по-вашему он не может быть зависимотипизированным.

Вы правда не понимаете о чем речь или это такой троллинг?

lambda x. x+1, 1 - это константа (терм, который не содержит свободных переменных), х - нет. Какое у нее известное на этапе компиляции (пусть даже эта ваша "компиляция" идет во время "интерпретации") значение? Если f - функция, то мы можем написать валидный терм lambda x. (f x)+1, с вашей f он не пишется, по-этому ваша f - не функция.
Edited Date: 2015-07-30 04:07 pm (UTC)

Date: 2015-07-30 04:15 pm (UTC)
From: [identity profile] thedeemon.livejournal.com
Где вы берете эти определения? Давайте возьмем тот же TAPL за основу, не будем тут придумывать чепухи.

Предполагать про лифтинг в SystemF - это и есть натягивание совы. Есть 1 и есть int, первое есть терм, второе тип. Есть интепретатор и есть компилятор, они оба сходятся, что 1 это терм. Но один из них умеет этот терм 1 отобразить в тип для второго обоих, например char[1]. Я рассматриваю не систему типов просто D (там завтипов нет), а систему типов интерпретируемого во время компиляции подмножества, это несколько отличающийся язык.
Edited Date: 2015-07-30 04:23 pm (UTC)

Date: 2015-07-30 04:21 pm (UTC)
From: [identity profile] thedeemon.livejournal.com
А вы выражайтесь яснее, то сами про "известными на момент компиляции" начали, я и подхватил.

Давайте по пунктам. Почему мою f нельзя применить к неконстантам?

Date: 2015-07-30 04:39 pm (UTC)
From: [identity profile] valentin budaev (from livejournal.com)
> А вы выражайтесь яснее, то сами про "известными на момент компиляции" начали, я и подхватил.

не-не, начали вы с "зависимыми типами в половине, доступной при компиляции", потом я подхватил а потом уже и вы. Значит, остановимся давайте на "вычисления на уровне типов", согласны?

> Давайте по пунктам. Почему мою f нельзя применить к неконстантам?

Потому что такая семантика у D. Ошибку выдает. Вычислений на уровне типов. Я специально проверил.

ЗЫ: на самом деле, кстати, мог бы и не выдавать, выразительных возможностей языка и системы типов это бы никак не изменило. Но раз уж выдает - грех на это не сослаться.
Edited Date: 2015-07-30 04:40 pm (UTC)

Date: 2015-07-30 04:54 pm (UTC)
From: [identity profile] valentin budaev (from livejournal.com)
> Есть 1 и есть int, первое есть терм, второе тип.

С этим, конечно, никто не спорит. Речь идет о том что ваша "1" в соответствующих контекстах ведет себя в точности как тип, конечно не Int, а какой-нибудь Succ Zero.

То есть, понимаете, есть лямбда-исчисление, в нем нету списков или там чисел с булами, но они моделируются на лямбдах. В практически применимых языках у нас и инты и буллы и списки со всем остальным есть, для удобства, но при этом системы эквивалентны.

Потом мы берем и вводим лямбда-исчисление на уровне типов. И тут есть два варианта - либо оставить лямбду (как в хаскеле), и тогда для того чтобы написать что-либо нетривиальное надо быть мудаком, либо добавить туда все те же булы, инты, списки да и вообще всю копию нашего языка, на котором эта система типов хостится. В этом случае мы можем писать нетривиальные вещи и не быть мудаками - вот так вот сделали в D. Но так же как лямбда без интов эквивалентна лямбде с интами так и тут. Это эквивалентные системы, просто не надо кодировать типы руками.

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

Ага, понял. Но все равно не сходится - с точки зрения этого подмножества integer или bool - это значения, с-но на уровне типов там обычная типизированная лямбда.

Date: 2015-07-30 04:57 pm (UTC)
From: [identity profile] thedeemon.livejournal.com
По пунктам, не спеша.

1. Речь не о системе типов всего D, а о системе типов подъязыка, доступного в CTFE. Именно поэтому я исходно начал про "при компиляции" - чтобы указать, о каком языке я вообще говорю.

2. В полноценном завтипизированном языке lambda x. (f x)+1 не пройдет проверку, т.к. f false возвращает строку. Давайте это исправим и возжелаем
lambda x. (f x, f x)
нет возражений?

Лямбд в указанном языке кажется нету, но ведь именованная функция тоже подойдет?

Пишем, работает:
http://dpaste.dzfl.pl/a35a7144df67

Date: 2015-07-30 05:13 pm (UTC)
From: [identity profile] thedeemon.livejournal.com
>Ага, понял. Но все равно не сходится - с точки зрения этого подмножества integer или bool - это значения, с-но на уровне типов там обычная типизированная лямбда.

Ура!
Там int и bool это и значения, и типы (т.к. использутся для "int sum = ..."). Ровно как и в Идрисе и подобных - там же тоже синтаксически только термы на самом деле, и терм : терм.
А вот что там (в CTFE подмножестве D) на уровне типов по-хорошему - вопрос мутный. Условный Идрис это или условный Питон - вот в чем вопрос.

Date: 2015-07-30 05:23 pm (UTC)
From: [identity profile] valentin budaev (from livejournal.com)
> Давайте это исправим и возжелаем
> lambda x. (f x, f x)
> нет возражений?

Да, конечно.

> Пишем, работает:

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

Но это вообще не суть, вы указали что речь не о D в целом, а конкретно о доступном во время компиляции подмножестве, и что там зависимые типы и такое все. Я в ответ вот, что сделаю, следите за руками, я беру обычный динамический лисп. Он динамический, везде динамический - и в рантайме и в компайлтайме, везде. Ну лисп, короче. И пишу вот такой вот код:
http://pastebin.com/DgvEYg8i

и он работает в точности как ваша f (вообще не совсем, подставлять надо именно значения #t/#f, переделать на любые constexpr на самом деле тривиально, просто не хочу загружать лишними особенностями работы макросистемы)

и получается как бы что у меня в компайлтайме зависимые типы? хотя язык компиляции полностью динамический

сразу наперед - вы можете заметить что раз основной язык динамический, то тут как бы зависимых типов и нет, потому что тип f на самом деле boolean->Any, а точнее и вовсе Any, на что легко возразить - достаточно заменить рантайм-язык простой типизированной лямбдой и оставить компайлтайм-язык без изменений, чтобы поправить эту ситуацию:
http://pastebin.com/XpdkN951
вот тот тип что указан после двоеточия - это именно статический тип, определяется во время компиляции. И вот выходит что у f зависимый тип и репл у меня тоже с зависимыми типами, прям как в идрисе, я все верно понимаю?
Edited Date: 2015-07-30 05:32 pm (UTC)

Date: 2015-07-30 05:56 pm (UTC)
From: [identity profile] valentin budaev (from livejournal.com)
> Там int и bool это и значения, и типы (т.к. использутся для "int sum = ...").

Ну так да, у вас же на уровне типов идет подмножество исходного языка, с-но, если в исходном типы есть - то и в этом будут. Получается, что на уровне типов мы имеем типы-значения и типы-типы (сорта), причем они совпадают в названиях, но не смешиваются, то есть это разные сущности. Смотрите ниже пример с лиспом, там динамический язык в компайлтайме по вашей логике зависимые типы имеет. Я при желании могу и в точности аналогичный D пример написать, лифтанув в 1-фазу ("компайлтайм" в терминологии Racket) типизированный диалект из 0-фазы, правда через костыль. Причем понятно же, что это тот же самый язык, что в 0-фазе, а раз он тот же самый - то и система типов та же самая, то есть простая типизированная лямбда. И в D таже логика, от того что вы язык "в компайлтайм лифтанули" он не стал другим языком (более выразительным).
Edited Date: 2015-07-30 06:01 pm (UTC)

Date: 2015-07-30 06:11 pm (UTC)
From: [identity profile] thedeemon.livejournal.com
Коммент с примерами на рэкете куда-то пропал. В почте я вижу, а тут нет.

Ну вот мы и пришли к правильным вопросам, тут рядом уже обсуждавшимся.

Date: 2015-07-31 06:06 am (UTC)
From: [identity profile] valentin budaev (from livejournal.com)
Странно, я пост вижу, вот:
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)
From: [identity profile] dima-starosud.livejournal.com
http://dpaste.dzfl.pl/ed67acba5aae

Date: 2015-07-31 03:09 pm (UTC)
From: [identity profile] zeit-raffer.livejournal.com
Я, конечно, извиняюсь, что вклиниваюсь в вашу оживленную беседу, но зачем всерьёз разговаривать с юродивыми? После первых пары комментов уже всё с человеком было ясно...

Date: 2015-07-31 06:39 pm (UTC)
From: [identity profile] thedeemon.livejournal.com
Да ладно, хороший человек и совершенно справедливо меня на чистую воду вывести старается. Да и не первый раз с ним беседуем.

Date: 2015-07-31 06:44 pm (UTC)
From: [identity profile] thedeemon.livejournal.com
Увы, по ссылке мне ЖЖ показывает лишь наличие того коммента, но раскрыть его не дает.
http://stuff.thedeemon.com/lj/susp_cmnt.png
А без ссылки и наличия не видно.

На остальное позже отвечу. Тут уже первый пункт сомнителен - считать подмножеством или это разные языки просто.

Re: Сделайте мне так:

Date: 2015-07-31 06:57 pm (UTC)
From: [identity profile] thedeemon.livejournal.com
void main(string[] argv) {
    enum x = import("argv.txt").length > 42;
    writeln( lam!x );
}


Аргументы коду на обсуждаемой части языка передаются либо через файлы, либо через ключи компиляции.

Date: 2015-08-01 09:49 am (UTC)
From: [identity profile] zeit-raffer.livejournal.com
Ну я не в том смысле, чтобы классифицировать данного человека, меня больше интересует это как явление. Не так редко встречаются подобные ветки, иногда на 10, иногда на 100 комментов. Оба собеседника вполне высказывают свою позицию в первых двух комментах, а потом ходят по кругу, повторяя свои аргументы. Я видел много таких диалогов, но не видел еще ни разу, чтобы кто-то кого-то таким образом убедил. По правилам шахмат, многократное повторение позиции должно защитываться как ничья обоим соперникам. Могу я спросить, зачем они (и Вы сейчас) это делают(-ете)?

Date: 2015-08-01 04:38 pm (UTC)
From: [identity profile] thedeemon.livejournal.com
Рекреационный спорт. :)

Date: 2015-08-05 07:43 am (UTC)
From: [identity profile] valentin budaev (from livejournal.com)
Вот:
#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 к аргументу.

Вопрос: это получились зависимые типы? Но я-то точно знаю, что там динамика :)
Edited Date: 2015-08-05 07:44 am (UTC)

Date: 2015-08-06 11:08 am (UTC)
From: [identity profile] thedeemon.livejournal.com
Я с Рэкетом не знаком, и тут не все конструкции понимаю.
Но один вопрос: почему проверяемое при компиляции "(: x Integer)" это динамика, а "x : Integer" в другом языке - статика?

Date: 2015-08-06 11:54 am (UTC)
From: [identity profile] valentin budaev (from livejournal.com)
> Я с Рэкетом не знаком, и тут не все конструкции понимаю.

На эти куски:
(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%, по-этому вопросы типа "а является ли оно подмножеством или не подмножеством или еще что...." нас не волнуют), и этот язык совершенно точно динамический.
Edited Date: 2015-08-06 12:00 pm (UTC)

Date: 2015-08-06 12:17 pm (UTC)
From: [identity profile] thedeemon.livejournal.com
Я ничего не понял. Какая разница на чем тайпчекер написан. Разница между динамическими и статическими типами отличается от разницы между динамической и статической проверкой типов, кстати.

Date: 2015-08-07 07:21 am (UTC)
From: [identity profile] valentin budaev (from livejournal.com)
> Какая разница на чем тайпчекер написан.

Он написан на "подмножестве языка, доступном при компиляции". Так что если мы обсуждаем "подмножество языка, доступное при компиляции", то мы обсуждаем язык, на котором написан тайпчекер.

> Разница между динамическими и статическими типами отличается от разницы между динамической и статической проверкой типов

Чем?

Date: 2015-09-09 09:16 pm (UTC)
From: [identity profile] udpn.livejournal.com
Вот к моему удивлению, я сегодня с Валентином согласен. Совсем не зависимые типы это в D, а другая, в чём-то схожая система типов. В завтипизированных языках можно полагаться в типах на зависимости от термов, значение которых в компайл-тайме не известно. Собственно, ради работы такой ереси и нужны все эти пруфы с тотальностью. Я сейчас могу даже привести пример задачи, которую можно решить завтипами, но нельзя в D.

Программа должна вводить из консоли сначала число 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.
Edited Date: 2015-09-09 09:18 pm (UTC)

Date: 2015-09-10 04:06 am (UTC)
From: [identity profile] thedeemon.livejournal.com
Ну вот, еще один не понял вообще о каком языке идет речь.
Я нигде и не говорил, что в D есть зависимые типы.

Date: 2015-09-10 04:09 am (UTC)
From: [identity profile] thedeemon.livejournal.com
А с Валентином я тоже согласен, но по несколько другим пунктам, он лучше меня понял.

Profile

thedeemon: (Default)
Dmitry Popov

December 2025

S M T W T F S
 12 3456
789101112 13
14151617181920
21222324252627
28293031   

Most Popular Tags

Page Summary

Style Credit

Expand Cut Tags

No cut tags
Page generated Jan. 25th, 2026 06:00 am
Powered by Dreamwidth Studios