Re[4]: Есть ли что-то о Constraint logic programming на Русс
От: Klapaucius  
Дата: 25.05.09 06:45
Оценка: 41 (2)
Здравствуйте, kl, Вы писали:

kl>Честно скажу, что в Немерле я не знаю ничего (да и вообще в языках с выводом типов), так что смогу чем-то помочь если удастся выделить задачу в некую абстрактную форму. Итак, было бы хорошо определиться с тем:

kl>1. Что является переменными (видимо символьные имена переменных в программе?)
kl>2. Что является доменом (областью допустимых значений) для каждой переменной (видимо множество всех типов?)
kl>3. Что представляют собой ограничения (т.е. в какой форме они задаются). Насколько я понимаю в них-то кодируются зависимости типов. В частности, очень интересно сколько (максимально и в среднем) переменных охватываются одним ограничением.
kl>Собственно если удастся выделить граф зависимостей в абстрактную форму CSP, то может я смогу посоветовать методы решения/оптимизации.

Магистерская работа Москаля о выводе типов в Nemerle здесь.
... << RSDN@Home 1.2.0 alpha 4 rev. 1110>>
'You may call it "nonsense" if you like, but I'VE heard nonsense, compared with which that would be as sensible as a dictionary!' (c) Lewis Carroll
Есть ли что-то о Constraint logic programming на Русском?
От: VladD2 Российская Империя www.nemerle.org
Дата: 22.05.09 15:28
Оценка:
Есть ли что почитать о Constraint logic programming и Constraint solver на Русском?
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re: Есть ли что-то о Constraint logic programming на Русском
От: kl Германия http://stardog.com
Дата: 23.05.09 14:53
Оценка:
Здравствуйте, VladD2, Вы писали:

VD>Есть ли что почитать о Constraint logic programming и Constraint solver на Русском?


Очень мало. Частично можно почитать о Моцарте или если есть под рукой, то некоторые главы отсюда.

Кстати, а что интересует о constraint solver'ах? Я как раз сейчас допиливаю собственный solver на основе Russian Doll Search, может смогу что-нибудь рассказать.
no fate but what we make
Re[2]: Есть ли что-то о Constraint logic programming на Русс
От: VladD2 Российская Империя www.nemerle.org
Дата: 24.05.09 22:33
Оценка:
Здравствуйте, kl, Вы писали:

kl>Очень мало. Частично можно почитать о Моцарте или если есть под рукой, то некоторые главы отсюда.


Спасибо, поглядим.

kl>Кстати, а что интересует о constraint solver'ах? Я как раз сейчас допиливаю собственный solver на основе Russian Doll Search,


Да, в общем-то все. Потому и ищу на русском, так как читать большие объемы проще на родном языке.

Конкретно, специализированный constraint solver используется в компиляторе Nemerle для вывода типов.
Так как вывод типов (включая разрешение перегрузки и отложенную типизацию) является самой сложной, запутанной и проблемной частью компилятора надо его отрефаторить. Но не понимая глубинных основ сделать это будет не просто.

kl>может смогу что-нибудь рассказать.


Было бы здорово. Найти достойных материалов на русском не просто.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[2]: Есть ли что-то о Constraint logic programming на Русс
От: VladD2 Российская Империя www.nemerle.org
Дата: 24.05.09 22:50
Оценка:
Здравствуйте, kl, Вы писали:

kl>Кстати, а что интересует о constraint solver'ах? Я как раз сейчас допиливаю собственный solver на основе Russian Doll Search, может смогу что-нибудь рассказать.


Собственно, интересует главным образом максимально эффективная реализация constraint solver-а (или оптимизация имеющегося) для вывода типов компилятора Nemerle.

В компиляторе с его помощью создается граф зависимостей типов внутри метода.
Главной проблемой является то, что в процессе типизации происходит множество спекулятивных типизаций (т.е. типизаций результаты которых нужны только для того чтобы понять возможен такой вариант типизации или нет). Особой проблемой это является в случае разрешения перегрузки где может быть множество вложенных вариантов типизации. Требуется как-то мемоизировать промежуточные вычисления чтобы повысить эффективность алгоритма. Грубо говоря нужно создать солвер который позволил бы, относительно дешево, создавать копии своего состояния и хранить их возвращаясь к ним в случае надобности. Кроме того желательно иметь возможность сливать состояние с предыдущим если нужно. Скажем, если у мы произвели спекулятивную типизацию и она удалась, то желательно слить это состояние с тем из которого оно ответвилось. Сейчас это невозможно и производится повторная типизация, что явно не эффективно.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[3]: Есть ли что-то о Constraint logic programming на Русс
От: kl Германия http://stardog.com
Дата: 24.05.09 22:59
Оценка:
Здравствуйте, VladD2, Вы писали:

VD>Да, в общем-то все. Потому и ищу на русском, так как читать большие объемы проще на родном языке.


VD>Конкретно, специализированный constraint solver используется в компиляторе Nemerle для вывода типов.

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

Ну если бы передо мной стояла задача реализации солвера, то я бы начал с анализа класса задачи (после ее формализации). Т.е:
а) имеем мы дело с CSP (constraint satisfaction problem) или COP (constraint optimization problem). Первый класс задач значительно проще и солверы намного лучше масштабируются;
б) какого рода ограничения используются. Это напрямую влияет на выбор алгоритма поиска решения. Есть понятие constraint propagation (т.е. насколько частичное присвоение значений переменным позволяет сделать заключение о выполнимости/невыполнимости/целевом значении ограничения в целом). Например, если переменные x и y — целые числа и есть ограничение x + y < 2, причем к этому моменту уже известно, что x = 2, то уже можно делать вывод, что ограничение невыполнимо и бэктрекиться. К сожалению на практике приходиться писать отдельные алгоритмы распространения под каждый тип ограничений.
c) если имеем дело с COP, то каков вид целевой функции.
д) как чаще всего выглядит граф ограничений. Имеет ли смысл применять алгоритмы декомпозиции для разделения задачи на подзадачи и их решения по отдельности.

Ну и т.д. и т.п. На данный момент известна чертова бездна различных оптимизаций (см. книжку Рины Дехтер по ссылке в Википедии, у меня она есть и она супер. Правда не думаю, что переводилась на русский).

kl>>может смогу что-нибудь рассказать.


VD>Было бы здорово. Найти достойных материалов на русском не просто.


Да я с радостью. Просто это настолько обширная область, то имеет смысл ее сузить :) А то к примеру такие задачи как раскраска графов, 8 ферзей или, например, о рюкзаке — это все задачи об ограничениях, но методы очень разные...
no fate but what we make
Re[3]: Есть ли что-то о Constraint logic programming на Русс
От: kl Германия http://stardog.com
Дата: 24.05.09 23:32
Оценка:
Здравствуйте, VladD2, Вы писали:

VD>Собственно, интересует главным образом максимально эффективная реализация constraint solver-а (или оптимизация имеющегося) для вывода типов компилятора Nemerle.


VD>В компиляторе с его помощью создается граф зависимостей типов внутри метода.

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

Ага, уже понятнее.
Честно скажу, что в Немерле я не знаю ничего (да и вообще в языках с выводом типов), так что смогу чем-то помочь если удастся выделить задачу в некую абстрактную форму. Итак, было бы хорошо определиться с тем:
1. Что является переменными (видимо символьные имена переменных в программе?)
2. Что является доменом (областью допустимых значений) для каждой переменной (видимо множество всех типов?)
3. Что представляют собой ограничения (т.е. в какой форме они задаются). Насколько я понимаю в них-то кодируются зависимости типов. В частности, очень интересно сколько (максимально и в среднем) переменных охватываются одним ограничением.
Собственно если удастся выделить граф зависимостей в абстрактную форму CSP, то может я смогу посоветовать методы решения/оптимизации.
А в качестве общих рекомендаций можно посоветовать только классику: препроцессинг, включающий в себя возможную декомпозицию, затем arc/path consistency, затем собственно решение поиском в глубину с отсечениями.
no fate but what we make
Re[5]: Есть ли что-то о Constraint logic programming на Русс
От: kl Германия http://stardog.com
Дата: 25.05.09 10:08
Оценка:
Здравствуйте, Klapaucius, Вы писали:

K>Магистерская работа Москаля о выводе типов в Nemerle здесь.


Спасибо, очень интересно, почитаю на досуге. Уже понятно, что их constraint solver несколько отличается от того, что как правило называется constraint solver'ом в литературе (в частности, используется система правил вместо систематического поиска).
Кстати, а на практике вообще заметна неполнота солвера? По идее это должно приводить к тому, что есть выражения, чей тип определен на основе семантики языка, но не выводится компилятором.
no fate but what we make
Re[4]: Есть ли что-то о Constraint logic programming на Русс
От: VladD2 Российская Империя www.nemerle.org
Дата: 25.05.09 13:20
Оценка:
Здравствуйте, kl, Вы писали:

kl>Ага, уже понятнее.

kl>Честно скажу, что в Немерле я не знаю ничего (да и вообще в языках с выводом типов), так что смогу чем-то помочь если удастся выделить задачу в некую абстрактную форму. Итак, было бы хорошо определиться с тем:
kl>1. Что является переменными (видимо символьные имена переменных в программе?)

Переменные типа. Типы в компиляторе делятся на два класса:
1. Фиксированные (пока что называются Mono-типами (MType), но в скором времени мы их переименуем в FixedType). Они являются целью вычисления.
2. Переменные типов. (пока что называются TyVar, но в ближайшее время видимо будут переименованы в VarType).
Далее я буду использовать будущие значения, так как они понятнее и читабельнее.

kl>2. Что является доменом (областью допустимых значений) для каждой переменной (видимо множество всех типов?)


Типы выводятся для одного метода. Так что доменом (если я правильно понимаю суть этого значения в данном применении) является список типов используемых в рамках одного тела методов.

kl>3. Что представляют собой ограничения (т.е. в какой форме они задаются). Насколько я понимаю в них-то кодируются зависимости типов. В частности, очень интересно сколько (максимально и в среднем) переменных охватываются одним ограничением.


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

Тут надо дать одно пояснение. В .net используется система типов поддерживающая наследование и интерфейсы. Nemerle выводит ближайший общий тип. Например, если у нас есть массив и список, то ближайшим общим типом для них будет IEnumerable[T] (интерфейс реализуемый обоими типами). Если же взять массив и класс динамически расширяемого массива List[T], то ближайшим общим типом для них будет IList[T], а не IEnumerable[T], так как IList[T] унаследован от IEnumerable[T], а значит является более частным (или большим или максимальным).


kl>Собственно если удастся выделить граф зависимостей в абстрактную форму CSP, то может я смогу посоветовать методы решения/оптимизации.


Я не владею этой терминологией. Поясни, плиз, чем отличаются CSP и COP.
Судя по звучанию мне кажется, что в нашем случае имеет место CSP, так как нам нужно не оптимизировать, а только построить граф зависимостей и ответить на вопрос не противоречив ли он. Что зе понимается под абстрактной формой я пока и сам не понимаю.

kl>А в качестве общих рекомендаций можно посоветовать только классику: препроцессинг, включающий в себя возможную декомпозицию, затем arc/path consistency, затем собственно решение поиском в глубину с отсечениями.



Дело в том, что информация вычисляется по мере постороения графа. Боюсь, что препроцессировать тут просто нечего.

В коде солвера, в комментариях, дано его описание:
http://nemerle.org/svn/nemerle/trunk/ncc/typing/Solver.n
Вот мой вольный перевод этого дела:

Есть два вида переменных типов:
* свободные переменные типов (free type variables) с ассоциированными верхней и нижней границей типа VladD2: Верхняя граница задает тип, которым как максимум должен являться тип переменной, а нижняя тип, которым как минимум должен являться тип переменной (т.е. абстрактнее которого не может быть тип переменной).
* фиксированная переменная типа, которая сопоставлена с конкретным типом.
Решатель ограничений (constraint solver) поддерживает граф переменных типов (вершин) и отношений подтипов/наследования (ребер). Граф следует нескольким инвариантам:
1. В нем есть только свободные переменные типов.
2. В нем нет циклов. В случае появления цикла, все переменные типов, вовлеченные в него, сливаются в одну переменную типа. Таким образом, граф является DAG-ом (направленным ациклическим графом).
3. Граф транзитивно замкнут, то есть, если A :> B и B :> C, то A :> C, где X :> Y означает ребро графа от X к Y.
4. Нижняя и верхняя граница также транзитивно замкнуты, то есть если t :> A, A :> B, B :> t', то t :> t', где :> означает отношения наследования (subtyping relation).
5. Если t :> A и A :> t', то t :> t' (то есть верхняя граница должна быть больше нижней). Если t = t', то тип t становится подстановкой для переменной А, поскольку есть только один тип, соответствующий и верхней, и нижней границе. При этом А становится фиксированной. Чтобы выполнялось правило 1, переменная удаляется из графа.
Иногда важно сохранить текущее состояние графа и затем вернуться к нему. Это делается с помощью методов PushState и PopState – они поддерживают стек отображений идентификаторов переменных типов на сами переменные типов.


Сам процесс построения графа производится следующим образом...
У класса VarType (он же пока что TyVar) есть методы:
  /// Определяет, что с этого времени @type, и this будет представлять 
  /// тот же самый тип. 
  public Unify(@type : VarType ) : bool;
  /// Тоже самое, что и Unify, но в случае неудачи не портит значения
  /// переменной типа и не влияет на состояние компилятора.
  public TryUnify(t : VarType ) : bool;

  /// Требовать чтобя this бытл по крайней мере типом [t] (или его 
  /// наследником). Вызывайте этот метод если требуется некоторое ограничение
  /// нижней границы типа.
  /// Возвращает true, если это возможно.
  /// Если это невозможно, то значение переменной типа будет испорчено и 
  /// может появиться сообщение об ошибке компилятора. Так что если нужно
  /// только только проверить возможность данной операции, то нужно 
  /// воспользоваться методом TryRequire
  public virtual Require(@type : VarType ) : bool;
  /// Тоже самое, что и Require, но в случае неудачи не портит значения
  /// переменной типа и не влияет на состояние компилятора.
  public TryRequire (@type : VarType ) : bool;

  /// Задать тип @type как максимальный тип для this.
  public virtual Provide (t : VarType ) : bool;
  /// Тоже самое, что и Provide, но в случае неудачи не портит значения
  /// переменной типа и не влияет на состояние компилятора.
  public TryProvide (@type : VarType ) : bool;

и свойства:
  /// Нижняя граница типа.  
  public LowerBound : option [FixedType];
  /// Верхняя граница типа.  
  public UpperBound : option [FixedType]


Применение методов Unify, Provide и Require влияет на значение свойств LowerBound и UpperBound переменных типов к которым применяются эти методы и всех переменных типов уже связанных с ними (т.е. к которым эти методы были применены ранее). Unify объяснять наверно толку нет, так как это полный аналог унификации. Он просто унифицирует верхнюю и нижнюю границы переменных. После его применения обе переменные становятся равны (равными становятся верхняя и нижняя границы). Provide накладывает ограничение на верхнюю границу типа (и/или нижнюю границу второго типа, но тут я еще плаваю). Как я понимаю Require — это "перевернутый" Provide, т.е. x.Provide(y) = y.Require(x).

Таким образом в графе какое-то время существуют свободные переменные (несвязанные) и по ходу дела к нему добавляются как свободные переменные, так и связи между ними (с помощью методов Unify, Provide и Require).
В итоге в графе должны остаться только связанные переменные. Если остаются несвязанные — это означает ошибку типизации (не выведенный тип).
По видимому граф для каждого тела метода предоставляется как набор несвязанных подграфов, так как возможно существование выражений чьи типы не пересекаются. Однако всегда возможно, что два, до какого-то момента, не связанных подграфа будут соединены отношением.

Методы PushState и PopState солвера должны обеспечивать сохранение и постановление текущего состояния графа соответственно. Наверно даже желательно расширить их функциональность и сделать возможность сохранять состояние записывая его в некую переменную, а потом восстанавливать состояние из этой переменно. Точно нужно иметь возможность сливать состояние. Сейчас отсутствие такой возможности приводит к тому, что одна и та же работа по типизации проделывается дважды просто потому, что нельзя использовать результаты типизации после вызова PopState.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[6]: Есть ли что-то о Constraint logic programming на Русс
От: WolfHound  
Дата: 25.05.09 13:27
Оценка:
Здравствуйте, kl, Вы писали:

kl>Кстати, а на практике вообще заметна неполнота солвера? По идее это должно приводить к тому, что есть выражения, чей тип определен на основе семантики языка, но не выводится компилятором.

Бывает. Но как правило лечится одним уточнением типа.
Пусть это будет просто:
просто, как только можно,
но не проще.
(C) А. Эйнштейн
Re[5]: Есть ли что-то о Constraint logic programming на Русс
От: VladD2 Российская Империя www.nemerle.org
Дата: 25.05.09 13:27
Оценка:
Здравствуйте, Klapaucius, Вы писали:

K>Магистерская работа Москаля о выводе типов в Nemerle здесь.


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

Проблама одна — тормоза.

Нужны оптимизации или более мощные алгоритмы.

Самая большая проблема формируется при сочетании тормозов солвера с тормозами вызванными вложенными спекулятивными типизациями при вычислении правильных перегрузок методов.

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

Одной из идей которая приходит мне в голову является мемоизация промежуточных результатов разрешения пергрузки, но боюсь, что без сохранения состояния солвера это будет невозможно реализовать.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[6]: Есть ли что-то о Constraint logic programming на Русс
От: VladD2 Российская Империя www.nemerle.org
Дата: 25.05.09 13:32
Оценка:
Здравствуйте, kl, Вы писали:

kl>Спасибо, очень интересно, почитаю на досуге. Уже понятно, что их constraint solver несколько отличается от того, что как правило называется constraint solver'ом в литературе (в частности, используется система правил вместо систематического поиска).

kl>Кстати, а на практике вообще заметна неполнота солвера? По идее это должно приводить к тому, что есть выражения, чей тип определен на основе семантики языка, но не выводится компилятором.

На практике проблем с выводом типов нет. На практике есть проблема производительности.

Я так понимаю, что сам Москаль (автор алгоритма) как раз хочет защитить Ph.D по поводу constraint solver-ов. Он даже вел какой-то открытый проект на том же Немерле.
Но в компиляторе используется весьма узкоспециализированный вариант солвера скорее похожий на обычный Пролог.
Задачу он свою решает неплохо, но к сожалению общая сложность кода получается так велика, что разбираться в нем не понимая тонких деталей трудно. Потому и ищу источники легкодоступной информации.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[5]: Есть ли что-то о Constraint logic programming на Русс
От: kl Германия http://stardog.com
Дата: 25.05.09 13:56
Оценка:
Здравствуйте, VladD2, Вы писали:

kl>>Собственно если удастся выделить граф зависимостей в абстрактную форму CSP, то может я смогу посоветовать методы решения/оптимизации.


VD>Я не владею этой терминологией. Поясни, плиз, чем отличаются CSP и COP.

VD>Судя по звучанию мне кажется, что в нашем случае имеет место CSP, так как нам нужно не оптимизировать, а только построить граф зависимостей и ответить на вопрос не противоречив ли он. Что зе понимается под абстрактной формой я пока и сам не понимаю.

Да, ты прав. Оптимизации тут нет, так что имеем CSP — надо просто найти какое-то решение, удовлетворяющее всем ограничениям (если таковое существует). Я правильно понимаю, что решение должно быть уникальным иначе это синтаксическая ошибка?
А насчет абстрактной формы я имел в виду стандартную формулировку CSP в виде совокупности трех компонентов (X,D,C) — переменных, доменов и ограничений.
С переменными и доменами в принципе понятно. Интереснее с ограничениями.
Каждое ограничение — это грубо говоря пара (t,R), где t — это набор переменных (чьи значения ограничиваются), а R — набор допустимых значений для этих переменных (т.е. какие значения эти переменные могут принимать *одновременно*).
Попробую привести грубый пример (сорри, если он по-дурацки выглядит в Немерле).

Есть некий метод, в нем определено 3 переменных типа (x,y,z) и есть 5 типов (A,B,C,D,E,F). Плюс насколько я понял над множеством типов определено что-то типа частичного порядка (скажем A < B, D < E < F < C, остальные типы несравнимы).
Так вот абстрактная форма CSP может выглядеть примерно так:
Переменные: {x,y,z}
Домены: {{A,B,C,D,E,F},...,{A,B,C,D,E,F}} (у всех переменных одинаковых домен — множество всех типов метода)
Ограничения: ({x,y},{{A,B},{D,F}}), ({y,z},{t(y) < t(z)}), ({x},{min{x} = D, max(x) = F}). Расшифровка: первое ограничение говорит: допустимыми парами типов для x и y являются {A,B} и {D,F}, второе: тип y должен быть меньше, чем тип z (и они сравнимы), третье: минимальным типом для x является D, максимальным — F.
Как-то так.
Решая эту CSP любым методом можем получить решение t(x) = D, t(y) = F, t(z) = C (если нигде не напутал).

Собственно мой вопрос заключается в том, можно ли проанализировав код метода сформулировать CSP в таком абстрактном виде? Если да — то можно применить фактически любой супер-оптимизированный CSP solver и получать результат (ну или написать свой).


kl>>А в качестве общих рекомендаций можно посоветовать только классику: препроцессинг, включающий в себя возможную декомпозицию, затем arc/path consistency, затем собственно решение поиском в глубину с отсечениями.



VD>Дело в том, что информация вычисляется по мере постороения графа. Боюсь, что препроцессировать тут просто нечего.


Интересно. Имеется в виду, что задача с ограничениями формулируется уже по ходу решения? Хм. Ладно, надо будет сначала почитать описание Москаля.
no fate but what we make
Re[6]: Есть ли что-то о Constraint logic programming на Русс
От: VladD2 Российская Империя www.nemerle.org
Дата: 25.05.09 17:36
Оценка:
Здравствуйте, kl, Вы писали:

kl>Да, ты прав. Оптимизации тут нет, так что имеем CSP — надо просто найти какое-то решение, удовлетворяющее всем ограничениям (если таковое существует). Я правильно понимаю, что решение должно быть уникальным иначе это синтаксическая ошибка?


Мне тяжело изъясняться в терминах "решение". Для меня как потребителя происходит процесс вывода типов.
Ошибкой является:
1. Когда в конце работы алгоритма типизации в графе имеются свободные переменные типа. Это значит, что для каких-то переменных или еще чего-то типы не были выведены.
2. Когда две переменные типа не прошли унификацию (или Requre/Provide, что тоже можно рассматривать как частный случай унификации).
3. Неоднозначность при разрешении перегрузки. Скажем есть две функции которые могут быть выведены при при текущих типах параметров (выражений передаваемых в качестве параметра).
4. Когда нет ни одной функции которая могла бы быть применена при текущих типах аргументов.

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

kl>А насчет абстрактной формы я имел в виду стандартную формулировку CSP в виде совокупности трех компонентов (X,D,C) — переменных, доменов и ограничений.

kl>С переменными и доменами в принципе понятно. Интереснее с ограничениями.

Доменом являются типы описанные в проекте или импортированные из других сборок, т.е. фиксирвоанные типы. Наверно, так.

Ограничениями же являются оношения между переменными.

Простой пример. Имеем две переменные инициализированные значениями:
    mutable a = System.Collections.Generic.List.[int]();
    mutable b = [1, 2] : System.Collections.IEnumerable;

Переменная а инициализируется значением динамического массива целых чисел (List.[int]() — это его конструктор с явным указанием параметра пипов).
Конструкция "X : Y" означает принудительное уточнение типа выражения X типом Y.
Так как список ([1, 2]) реализует интерфейс System.Collections.IEnumerable, то унификация проходит успешно и мы получаем следущее значение переменных типов для этих переменных:
a is 'System.Collections.Generic.List[int]-'
   LowerBound: 'None'
   UpperBound: 'Some (System.Collections.Generic.List[int])'

Теперь напишем простенький макрос который будет печатать состояние переменных типа, производить унификаци и выводить результат:
public macro Macro1(arg1, arg2)
{
  Impl.Macro1(Nemerle.Macros.ImplicitCTX(), arg1, arg2)
}

module Impl
{
  public Macro1(typer : Typer, arg1 : PExpr, arg2 : PExpr) : PExpr
  {
    def printInfo(expr : PExpr, ty : TyVar) : void
    {
      Message.Hint($"$expr is '$ty'");
      
      Message.Hint($"  LowerBound: '$(ty.LowerBound)'");
      Message.Hint($"  UpperBound: '$(ty.UpperBound)'");
    }
  
    def tExpr1 = typer.TypeExpr(arg1);
    def tExpr2 = typer.TypeExpr(arg2);

    printInfo(arg1, tExpr1.Type);
    printInfo(arg2, tExpr2.Type);
    
    _ = tExpr2.Type.Unify(tExpr1.Type);
    
    Message.Hint("После Unify:");
    
    printInfo(arg1, tExpr1.Type);
    printInfo(arg2, tExpr2.Type);
    
    tExpr1.Type.Fixate();
    tExpr2.Type.Fixate();

    Message.Hint("После Fixate:");
    
    printInfo(arg1, tExpr1.Type);
    printInfo(arg2, tExpr2.Type);

     <[ () ]>
  }
}

И используем его в код:
    mutable a = System.Collections.Generic.List.[int]();
    mutable b = [1, 2] : System.Collections.IEnumerable;
    
    Macro1(a, b);

Если скомпилировать этот код, то на консоль будет выведено:
a is 'System.Collections.Generic.List[int]-'
  LowerBound: 'None'
  UpperBound: 'Some (System.Collections.Generic.List[int])'
b is 'System.Collections.IEnumerable'
  LowerBound: 'Some (System.Collections.IEnumerable)'
  UpperBound: 'Some (System.Collections.IEnumerable)'
После Unify:
a is 'System.Collections.IEnumerable'
  LowerBound: 'Some (System.Collections.IEnumerable)'
  UpperBound: 'Some (System.Collections.IEnumerable)'
b is 'System.Collections.IEnumerable'
  LowerBound: 'Some (System.Collections.IEnumerable)'
  UpperBound: 'Some (System.Collections.IEnumerable)'
После Fixate:
a is 'System.Collections.IEnumerable'
  LowerBound: 'Some (System.Collections.IEnumerable)'
  UpperBound: 'Some (System.Collections.IEnumerable)'
b is 'System.Collections.IEnumerable'
  LowerBound: 'Some (System.Collections.IEnumerable)'
  UpperBound: 'Some (System.Collections.IEnumerable)'


Если заменить в коде макроса "_ = tExpr2.Type.Unify(tExpr1.Type);" (выделено жирным) на "_ = tExpr1.Type.Require(tExpr2.Type);", то мы получим следующий результат:
 a is 'System.Collections.Generic.List[int]-'
   LowerBound: 'None'
   UpperBound: 'Some (System.Collections.Generic.List[int])'
 b is 'System.Collections.IEnumerable'
   LowerBound: 'Some (System.Collections.IEnumerable)'
   UpperBound: 'Some (System.Collections.IEnumerable)'
 После Require:
 a is '(System.Collections.IEnumerable TILL System.Collections.Generic.List[int])'
   LowerBound: 'Some (System.Collections.IEnumerable)'
   UpperBound: 'Some (System.Collections.Generic.List[int])'
 b is 'System.Collections.IEnumerable'
   LowerBound: 'Some (System.Collections.IEnumerable)'
   UpperBound: 'Some (System.Collections.IEnumerable)'
 После Fixate:
 a is 'System.Collections.Generic.List[int]'
   LowerBound: 'Some (System.Collections.Generic.List[int])'
   UpperBound: 'Some (System.Collections.Generic.List[int])'
 b is 'System.Collections.IEnumerable'
   LowerBound: 'Some (System.Collections.IEnumerable)'
   UpperBound: 'Some (System.Collections.IEnumerable)'

Если применить Require в обратную сторону, т.е.: "_ = tExpr2.Type.Require(tExpr1.Type);", то получится:
a is 'System.Collections.Generic.List[int]-'
  LowerBound: 'None'
  UpperBound: 'Some (System.Collections.Generic.List[int])'
b is 'System.Collections.IEnumerable'
  LowerBound: 'Some (System.Collections.IEnumerable)'
  UpperBound: 'Some (System.Collections.IEnumerable)'
После Require:
a is 'System.Collections.IEnumerable-'
  LowerBound: 'None'
  UpperBound: 'Some (System.Collections.IEnumerable)'
b is 'System.Collections.IEnumerable'
  LowerBound: 'Some (System.Collections.IEnumerable)'
  UpperBound: 'Some (System.Collections.IEnumerable)'
После Fixate:
a is 'System.Collections.IEnumerable'
  LowerBound: 'Some (System.Collections.IEnumerable)'
  UpperBound: 'Some (System.Collections.IEnumerable)'
b is 'System.Collections.IEnumerable'
  LowerBound: 'Some (System.Collections.IEnumerable)'
  UpperBound: 'Some (System.Collections.IEnumerable)'


kl>Каждое ограничение — это грубо говоря пара (t,R), где t — это набор переменных (чьи значения ограничиваются), а R — набор допустимых значений для этих переменных (т.е. какие значения эти переменные могут принимать *одновременно*).


Как я понимаю, ограничением в данном случае является значение верхней и нижней границы типа, то есть значение свойств LowerBound и UpperBound. А выражаются они в связи с другой переменной.

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

kl>Попробую привести грубый пример (сорри, если он по-дурацки выглядит в Немерле).


kl>Есть некий метод, в нем определено 3 переменных типа (x,y,z) и есть 5 типов (A,B,C,D,E,F). Плюс насколько я понял над множеством типов определено что-то типа частичного порядка (скажем A < B, D < E < F < C, остальные типы несравнимы).

kl>Так вот абстрактная форма CSP может выглядеть примерно так:
kl>Переменные: {x,y,z}
kl>Домены: {{A,B,C,D,E,F},...,{A,B,C,D,E,F}} (у всех переменных одинаковых домен — множество всех типов метода)
kl>Ограничения: ({x,y},{{A,B},{D,F}}), ({y,z},{t(y) < t(z)}), ({x},{min{x} = D, max(x) = F}). Расшифровка: первое ограничение говорит: допустимыми парами типов для x и y являются {A,B} и {D,F}, второе: тип y должен быть меньше, чем тип z (и они сравнимы), третье: минимальным типом для x является D, максимальным — F.
kl>Как-то так.
kl>Решая эту CSP любым методом можем получить решение t(x) = D, t(y) = F, t(z) = C (если нигде не напутал).

Что-то вроде этого, только ограничениями являются исключительно связи между типами. Связи могут быть только на больший и меньший тип. Фактический тип вычисляется так:
if (tyVar.UpperBound.IsSome)
  tyVar.UpperBound
else if (tyVar.LowerBound.IsSome)
  tyVar.LowerBound
else
  None()

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

kl>Собственно мой вопрос заключается в том, можно ли проанализировав код метода сформулировать CSP в таком абстрактном виде? Если да — то можно применить фактически любой супер-оптимизированный CSP solver и получать результат (ну или написать свой).


Как я понимаю, в работе Москаля на которую сослался Klapaucius:
http://www.rsdn.ru/forum/decl/3402298.1.aspx
Автор: Klapaucius
Дата: 25.05.09

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

VD>>Дело в том, что информация вычисляется по мере постороения графа. Боюсь, что препроцессировать тут просто нечего.


kl>Интересно. Имеется в виду, что задача с ограничениями формулируется уже по ходу решения? Хм. Ладно, надо будет сначала почитать описание Москаля.


Именно. Ну, если я конечно "в теме" .
Выше я привел пример с двумя переменными и их унификацией.
Так вот сразу после наложения связи (вызова методов Unify, Require или Provide значение графа изменяется и мы сразу видим его результат. Это очень похоже на интерпретатор пролога когда мы задавая некоторое правило сразу же можем получить уточненное значение некоторых перменных (которые затрагиваются этим правилом).

Фактически конечного решения как такового не существует! Решением является отсуствие свободных переменных.
Можно сказать, что алгоритм выводит "хоть какие-то типы" .
Реально за достаточность типов отвечает объект Typer и объекты отложенной типизации. Когда Typer встречает выражение тип которого неизвестен, но нужен, Typer вместо порождения типизированного АСТ возвращает объект отложенной типизации. Причем этот объект лишь интерфйс к реальному отбъекту хранимому в очеред Солвера.
Далее типизатор тупо продолжает типизацию остального кода.
В конце процесса типизации Typer смотрит есть ли объекты отложенной типизации в Солвере и если есть, то при казывает им попытаться саморазрешиться с текущим значением графа типов. Если хотя бы один объект отложенной типизации сможет разрешиться он вынимается из очереди и проверяется не пуста ли очередь. Если очередь не пуста, то происходит еще одна попытка типизировать объекты отоженной типизации. Так продолжается до тех пор пока очередь не будет пуста или пока в очреде не будет изменений. Последнее означает, что типизация окончилась неудачей. Прервое — удачей.
Таким образом конечным состоянием графа типов является просто состояние при котором смогли вычилиться все объекты отоженной типизации из очереди оных. Как я понимаю, это совпадает с тем фактом, что в графе типов не остается свободных типов (типов у которых не задно ни верхнрей, ни нижней границы).
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[7]: Есть ли что-то о Constraint logic programming на Русс
От: kl Германия http://stardog.com
Дата: 25.05.09 23:28
Оценка:
Здравствуйте, VladD2, Вы писали:


kl>>Собственно мой вопрос заключается в том, можно ли проанализировав код метода сформулировать CSP в таком абстрактном виде? Если да — то можно применить фактически любой супер-оптимизированный CSP solver и получать результат (ну или написать свой).


VD>Как я понимаю, в работе Москаля на которую сослался Klapaucius:

VD>http://www.rsdn.ru/forum/decl/3402298.1.aspx
Автор: Klapaucius
Дата: 25.05.09

VD>как раз и описывается формально как алгоритм солвера, так и общий алгоритм вывода типов. Но лично для меня это описание слишком абстрано и вследствии некоторых пробелов в знаниях я не все в нем понимаю.

Начал я читать работу Москаля, правда вечер понедельника — не самое лучшее время, поэтому особенно не углубился пока. В принципе идея deferred typing понятна. Другое дело, что я пока не понял ее 100% необходимость (кроме как для оптимизации). Есть стойкое ощущение, что все должно решаться и так.
Еще раз извиняюсь за незнание Немерле, но попробуем рассмотреть первый же пример в статье (3-я страница вверху):
let invoke = \lambda x. x.some_method_of_foo() in
let a_list = [make_foo(1), make_foo(2), make_foo(3)] in
List.Map(a_list, invoke)

Итак, Москаль утверждает, что проблема в том, как типизировать функцию invoke, и предлагает это дело отложить до тех пор, пока мы не дойдем до ее использования в List.Map. Но в принципе ничто не заставляет нас постепенно выводить типы, мне кажется все сводится к обычной CSP примерно так:
Имеем переменные: t_invoke, t_list_elem, t_map_list_elem, t_map_func (соответственно для типа invoke, элемента списка, элемента списка в Map и функции в Map)
Имеем домены для каждой переменной: для t_invoke это будет все пары типов <A,B> (т.к. функция у нас одноаргументная A -> B), для t_list_elem — все типы метода, для t_map_list_elem аналогично t_list_elem, для t_map_func аналогично t_invoke.
Теперь самое интересное — ограничения.
1) очевидное: значение t_list_elem должно быть больше/равно типа функции make_foo
2) значение t_map_list_elem должно быть больше/равно t_list_elem
3) первый компонент (А) значения t_invoke должен быть больше/равен t_map_list_elem
4) второй компонент (B) значения t_invoke должен быть равен типу A.some_method_of_foo (тут, кстати, можно сразу сделать propagation этого ограничения, сказав, что A может быть только типом, у которого есть метод some_method_of_foo)
Извиняюсь если сильно напутал с больше/меньше (поздно уже).
Собственно все. Решая эту CSP относительно всех доступных (фиксированных, импортируемых — вам видней) типов метода, мы должны получить решение. Решением будет присвоение значений всем переменным (в том числе выясним и тип invoke).
При этом вся отложенная типизация ложится на плечи CSP солвера. Практически *все* нормальные солверы реализуют эвристику fail first, т.е. они пытаются сначала определиться со значениями наиболее ограниченных переменных. В данном случае это, ясно дело, будет t_list_elem, поскольку тут свободы нет — все определяется типом make_foo. Затем будут присвоены значения t_map_list_elem (следующей самой ограниченной), ну и соответственно t_map_func и t_invoke.
При этом если в ограничения удастся запихнуть всю семантику языка, то солвер будет полным (в отличие от того, что есть сейчас) и уточнений типа не понадобится. Допускаю, что по ряду причин это может не получиться сделать.
Собственно, думаю, что и Москаль понимал, что в теории солвер должен справляться и так, без внешних очередей для отложенных переменных и т.д. Возможно это оптимизация. Эта штука в общем-то тоже неплохо изучена, называется Dynamic CSP, когда ограничения добавляются динамически. Тут вся фишка в переиспользовании предыдущего решения системы ограничений (т.е. выведенных типов до добавления новых зависимостей). Иногда это еще называют "горячим стартом". Это делается потому, что есть шанс, что новое решение будет лежать где-то близко от существующего. Советую посмотреть работу Solution reuse in dynamic constraint satisfaction problems
Пока все, на выходных попробую почитать дальше (чувствую, правда, придется Немерле изучать :( ).
no fate but what we make
Re[8]: Есть ли что-то о Constraint logic programming на Русс
От: VladD2 Российская Империя www.nemerle.org
Дата: 31.05.09 21:55
Оценка:
Здравствуйте, kl, Вы писали:

kl>Начал я читать работу Москаля, правда вечер понедельника — не самое лучшее время, поэтому особенно не углубился пока. В принципе идея deferred typing понятна. Другое дело, что я пока не понял ее 100% необходимость (кроме как для оптимизации). Есть стойкое ощущение, что все должно решаться и так.

kl>Еще раз извиняюсь за незнание Немерле, но попробуем рассмотреть первый же пример в статье (3-я страница вверху):
kl>
kl>let invoke = \lambda x. x.some_method_of_foo() in
kl>let a_list = [make_foo(1), make_foo(2), make_foo(3)] in
kl>List.Map(a_list, invoke)
kl>


Это код не на немерле, а на некотором гипотетическом языка с базовым синтаксисом ML-я.
Тот же код на Немерле будет выглядеть такЖ
def invoke = x => x.some_method_of_foo(); // аналог: def invoke(x) { x.some_method_of_foo() }
def a_list = [make_foo(1), make_foo(2), make_foo(3)];
a_list.Map(invoke) // аналог: List.Map(a_list, invoke)


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


Заставляет и об этом сказано в статье далее. Заставляют макросы. Макросы раскрываются во время типзации и они могут сами пользоваться плодами типизации (естественно в объеме доступном на момент их раскрытия).

kl>мне кажется все сводится к обычной CSP примерно так:

kl>Имеем переменные: t_invoke, t_list_elem, t_map_list_elem, t_map_func (соответственно для типа invoke, элемента списка, элемента списка в Map и функции в Map)

Не все так просто. a_list.Map может быть чем угодно, а не только методом. Например, a_list.Map может быть полем или свойством которые возвращают функциональный тип. Так что чтобы ответеть на вопрос "что такое a_list.Map?" сначала нужно ответить на вопрос "что такое a_list?".

kl>Имеем домены для каждой переменной: для t_invoke это будет все пары типов <A,B> (т.к. функция у нас одноаргументная A -> B),


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

kl>для t_list_elem — все типы метода, для t_map_list_elem аналогично t_list_elem, для t_map_func аналогично t_invoke.

kl>Теперь самое интересное — ограничения.
kl>1) очевидное: значение t_list_elem должно быть больше/равно типа функции make_foo

+1 и это элементарно вычисляется солвером

kl>2) значение t_map_list_elem должно быть больше/равно t_list_elem


Не совсем. Тут может получиться перегрузка. Как я уже говорил Map может быть не методом, плюс Map может быть перегружен и может потребоваться выбор одного из вариантов перегрузки.
В текущей логике сейчас сначала для a_list.Map создается объект отложенной типизации типа "доступ к члену" и дальнейшая типизация производится с целью выяснить что за член имеется в виду в данном случае.
Обычно отложенной типизации "доступ к члену" при типизации раскрывается в отложенной типизации "разрешение перегрузки". Вот тут то и наступает самое интересное. Ведь качестве параметров методу передаются выражения которые так же содержат отложенной типизации. Получается комбинирование всех возможных вариантов. Будучи помноженном на время работы солвера и на два (так как все спекулятивные операции выполняются как минимум два раза, один раз как спекулятивные, а второй как нормальные) получаются весьма ощутимые тормоза.

kl>3) первый компонент (А) значения t_invoke должен быть больше/равен t_map_list_elem

kl>4) второй компонент (B) значения t_invoke должен быть равен типу A.some_method_of_foo (тут, кстати, можно сразу сделать propagation этого ограничения, сказав, что A может быть только типом, у которого есть метод some_method_of_foo)

Тоже плохо, так как вывод типов работает и для выдачи списков автдополнения в IDE. Если полагаться на имеющиеся методы, то автодополнение работать не будет.

kl>Извиняюсь если сильно напутал с больше/меньше (поздно уже).

kl>Собственно все. Решая эту CSP относительно всех доступных (фиксированных, импортируемых — вам видней) типов метода, мы должны получить решение. Решением будет присвоение значений всем переменным (в том числе выясним и тип invoke).

Как я уже говорил, в коде могут быть макросы. Они не могут ждать когда будут выведены все типы. А при нераскрытых макрах типы могут вообще никогда не вывестись.

kl>При этом вся отложенная типизация ложится на плечи CSP солвера. Практически *все* нормальные солверы реализуют эвристику fail first, т.е. они пытаются сначала определиться со значениями наиболее ограниченных переменных. В данном случае это, ясно дело, будет t_list_elem, поскольку тут свободы нет — все определяется типом make_foo. Затем будут присвоены значения t_map_list_elem (следующей самой ограниченной), ну и соответственно t_map_func и t_invoke.


То есть предлагается сначала пробежаться по коду и заполнить солвер переменными и ограничениями, а потом один раз "решить уравнение"?

Тут есть две проблемы:
1. Упомянутые выше макросы.
2. Вложенные выражения, перегрузка, неявное приведение типов. Уже в коде:
mutable z;
x.a(y.b(x));
z = "str";

будет не ясно какие зависимости накладывать на y.b если x.a перегружен. Предположим, что x имеет тип X, а y соответственно Y:
class X
{
  public a(i : int)    : void { ... }
  public a(s : string) : void { ... }
}
class Y
{
  public b(i : int)    : string { ... }
  public b(s : string) : int    { ... }
  public b : dioble = 1.1.
}

Получаются разные пути решения. Они зависят от того, что за тип имеет z. Получается, что пока не будет известен тип z, мы не может даже предполжить что за тип имеет y.b. Таким образом мы можем предполагать что x.a ==> X.a : int -> void, при этом y.b должен быть Y.b : string -> int, а z : string, или x.a ==> X.a : string, при этом y.b должен быть Y.b : int -> string, а z : int.

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

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

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


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

kl>Допускаю, что по ряду причин это может не получиться сделать.

kl>Собственно, думаю, что и Москаль понимал, что в теории солвер должен справляться и так, без внешних очередей для отложенных переменных и т.д. Возможно это оптимизация. Эта штука в общем-то тоже неплохо изучена, называется Dynamic CSP, когда ограничения добавляются динамически. Тут вся фишка в переиспользовании предыдущего решения системы ограничений (т.е. выведенных типов до добавления новых зависимостей).

Ага, что-то вроде этого. Причем эта динамичность позволяет принимать некоторые решения до момента получения полного решения.

kl> Иногда это еще называют "горячим стартом". Это делается потому, что есть шанс, что новое решение будет лежать где-то близко от существующего. Советую посмотреть работу Solution reuse in dynamic constraint satisfaction problems


А можно создать такой динамический солвер который будет не только позволять рсширять решение, то и делать несколько альтернативных расширений для некоторой точки, а потом принимать дно из решений как верное и развивать его дальше, а все остальные отбрасывать?

kl>Пока все, на выходных попробую почитать дальше (чувствую, правда, придется Немерле изучать ).


Ну, как? Получилось прочесть?
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[9]: Есть ли что-то о Constraint logic programming на Русс
От: kl Германия http://stardog.com
Дата: 01.06.09 00:04
Оценка:
Здравствуйте, VladD2, Вы писали:

kl>>
kl>>let invoke = \lambda x. x.some_method_of_foo() in
kl>>let a_list = [make_foo(1), make_foo(2), make_foo(3)] in
kl>>List.Map(a_list, invoke)
kl>>


VD>Это код не на немерле, а на некотором гипотетическом языка с базовым синтаксисом ML-я.

VD>Тот же код на Немерле будет выглядеть такЖ

Ну да, но для меня они пока выглядят все на одно лицо :)

VD>Заставляет и об этом сказано в статье далее. Заставляют макросы. Макросы раскрываются во время типзации и они могут сами пользоваться плодами типизации (естественно в объеме доступном на момент их раскрытия).


Ага, я и подозревал, что все сложнее... Поэтому и писал "допускаю, что по ряду причин это может не получиться сделать".

VD>То есть предлагается сначала пробежаться по коду и заполнить солвер переменными и ограничениями, а потом один раз "решить уравнение"?


Ну не уравнение, а CSP, но в *теории* да. Понимаешь, я плохо знаком с семантическими анализаторами кода, я просто говорю из своего опыта решения CSP. Если можно максимально запихнуть ограничений в солвер и решить один раз — то это надо обязательно делать. Современные методы позволяют извлекать преимущества из большого количества ограничений — они позволяют быстрее определяться со значениями переменных. Грубо говоря на практике часто встречается такая картина:
— При малом числе ограничений CSP решается легко
— Потом при увеличении числа ограничений производительность падает, поскольку солвер начинает слишком часто бэктрекиться.
— Если число ограничений продолжает расти, то в какой-то момент производительность опять улучшается, поскольку некоторые переменные оказываются настолько ограниченными, что солвер быстро определяет их значения (т.е. пространство решений сжимается).
Так что хочу сказать, что все эти динамические CSP и т.д. надо применять только тогда, когда 100% не получается обойтись классическим вариантом. Может тут именно такой случай.

kl>> Иногда это еще называют "горячим стартом". Это делается потому, что есть шанс, что новое решение будет лежать где-то близко от существующего. Советую посмотреть работу Solution reuse in dynamic constraint satisfaction problems


VD>А можно создать такой динамический солвер который будет не только позволять рсширять решение, то и делать несколько альтернативных расширений для некоторой точки, а потом принимать дно из решений как верное и развивать его дальше, а все остальные отбрасывать?


Он не "расширяет" решение, он находит новое. Работает так:
1. На каком-то этапе есть сформированная CSP. Солвер ее решает и выдает решение — присвоение значений всем переменным, не нарушающее ограничения.
2. Внешний код получает это решение, анализирует его и выясняет, что это решение по какой-то причине не подходит. Эту "какую-то причину" он формулирует в виде дополнительного ограничения, добавляет его в CSP и говорит солверу: условия изменились, реши еще раз.
3. Солвер с горячим стартом не решает заново. Он смотрит значения каких переменных не удовлетворяют новому ограничению и пытается сделать т.н. local repair, т.е грубо говоря "пофиксить" предыдущее решение. Это получается чаще чем не получается.

То, что ты имеешь в виду под "точкой", скорее всего представляет собой обыкновенную промежуточную вершину (этап) поиска в глубину (а CSP часто решаются именно так). Предположим есть переменные a,b,c (переменные типа) и домен для каждого (X,Y,Z — набор типов). Плюс какие-то ограничения. Солвер будет решать так:
1. Присвоили a значение (тип) X. Проверили ограничения
2. Если пока все ок, то ветвимся (3 ветки — по одной на каждый возможный тип b). Переменной b присваиваем тип X (левая ветка), Y (средняя ветка) или Z (правая ветка)
Дерево выглядит примерно так (растет вниз и вправо):

a
|-X-b
|   |-X-c
|   |   |...
|   |-Y-c
|   |   |...
|   |-Z-c
|       |...
|-Y-b
|   |...
...
|-Z-b
    |...


Если дошли до листа и не нарушили хотя бы одно ограничение — решение найдено. Если же на каком-то этапе нарушили — бэктрекимся и пробуем следующее значение (тип) для переменной. Плюс препроцессинг, миллион эвристик и всяческих оптимизаций.
Таким образом, твоя "точка" — это узел, в котором для части переменных типы уже выведены, а для части — еще нет. Пока ограничения не нарущены, но нет гарантии, что они не будут нарушены далее. При поиске в глубину сохраняется линейное число узлов — от вершины до текущего. Можно конечно сохранять агрессивнее.
Один примитивный способ — это взять и присвоить отложенным переменным случайные типы (или использоватаь None в качестве зарезервированного типа для этого). Далее в процессе анализа (когда твои макросы развернутся) будут добавлены новые ограничения, которые заставят солвер заменить случайные значения (или None) на тип, который будет им удовлетворять.

kl>>Пока все, на выходных попробую почитать дальше (чувствую, правда, придется Немерле изучать :( ).


VD>Ну, как? Получилось прочесть?


Неа, дедлайны, блин, постараюсь завтра-послезавтра.
no fate but what we make
Re[10]: Есть ли что-то о Constraint logic programming на Рус
От: VladD2 Российская Империя www.nemerle.org
Дата: 01.06.09 04:29
Оценка:
Здравствуйте, kl, Вы писали:

kl>Так что хочу сказать, что все эти динамические CSP и т.д. надо применять только тогда, когда 100% не получается обойтись классическим вариантом. Может тут именно такой случай.


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

VD>>А можно создать такой динамический солвер который будет не только позволять расширять решение, то и делать несколько альтернативных расширений для некоторой точки, а потом принимать дно из решений как верное и развивать его дальше, а все остальные отбрасывать?


kl>Он не "расширяет" решение, он находит новое. Работает так:

kl>1. На каком-то этапе есть сформированная CSP. Солвер ее решает и выдает решение — присвоение значений всем переменным, не нарушающее ограничения.
kl>2. Внешний код получает это решение, анализирует его и выясняет, что это решение по какой-то причине не подходит. Эту "какую-то причину" он формулирует в виде дополнительного ограничения, добавляет его в CSP и говорит солверу: условия изменились, реши еще раз.
kl>3. Солвер с горячим стартом не решает заново. Он смотрит значения каких переменных не удовлетворяют новому ограничению и пытается сделать т.н. local repair, т.е грубо говоря "пофиксить" предыдущее решение. Это получается чаще чем не получается.

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

И еще... У меня такой вопрос...

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

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

kl>То, что ты имеешь в виду под "точкой", скорее всего представляет собой обыкновенную промежуточную вершину (этап) поиска в глубину (а CSP часто решаются именно так). Предположим есть переменные a,b,c (переменные типа) и домен для каждого (X,Y,Z — набор типов). Плюс какие-то ограничения. Солвер будет решать так:

kl>1. Присвоили a значение (тип) X. Проверили ограничения

Типов может быть довольно много. Проверять их все перебором не лучшая идея.

kl>2. Если пока все ок, то ветвимся (3 ветки — по одной на каждый возможный тип b). Переменной b присваиваем тип X (левая ветка), Y (средняя ветка) или Z (правая ветка)

kl>Дерево выглядит примерно так (растет вниз и вправо):

kl>
kl>a
kl>|-X-b
kl>|   |-X-c
kl>|   |   |...
kl>|   |-Y-c
kl>|   |   |...
kl>|   |-Z-c
kl>|       |...
kl>|-Y-b
kl>|   |...
kl>...
kl>|-Z-b
kl>    |...
kl>


kl>Если дошли до листа и не нарушили хотя бы одно ограничение — решение найдено. Если же на каком-то этапе нарушили — бэктрекимся и пробуем следующее значение (тип) для переменной. Плюс препроцессинг, миллион эвристик и всяческих оптимизаций.


Бэктрэкинг в условиях неявного приведения типов, наследования типов, вложенных перегруженных вызовах — это верных путь к экспоненциальному росту затрат процессорного времени.

kl>Таким образом, твоя "точка" — это узел, в котором для части переменных типы уже выведены, а для части — еще нет. Пока ограничения не нарущены, но нет гарантии, что они не будут нарушены далее. При поиске в глубину сохраняется линейное число узлов — от вершины до текущего. Можно конечно сохранять агрессивнее.


А как откатываться к предыдущему состоянию? Или (что еще лучше) производить параллельные поиски решений?

kl>Один примитивный способ — это взять и присвоить отложенным переменным случайные типы (или использоватаь None в качестве зарезервированного типа для этого). Далее в процессе анализа (когда твои макросы развернутся) будут добавлены новые ограничения, которые заставят солвер заменить случайные значения (или None) на тип, который будет им удовлетворять.


Дык, как я понимаю сейчас так и делается. Только разворачивание макросов происходит перед типизацией, а сами переменные создаются и инициализирующий в процессе типизации. Но это приводит к тому, что при типизации каждой ветки АСТ происходит постоянное изменение графа типов и автоматическое его пересчитывание. Скажем если у нас есть переменные типов tv1, tv2, tv3 и tv1 связана с tv2 по нижней границе tv2, то при добавлении связи между tv2 и tv3 создается транзитивная зависимость между всеми тремя типами.

kl>Неа, дедлайны, блин, постараюсь завтра-послезавтра.


ОК, жду. Очень интересно мнение человека который в этом что-то понимает.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[11]: Есть ли что-то о Constraint logic programming на Рус
От: kl Германия http://stardog.com
Дата: 01.06.09 08:02
Оценка:
Здравствуйте, VladD2, Вы писали:

VD>Бэктрэкинг в условиях неявного приведения типов, наследования типов, вложенных перегруженных вызовах — это верных путь к экспоненциальному росту затрат процессорного времени. :(


Остальные комментарии пока попридержу (пока не дочитаю Москаля), замечу только, что современные солверы могут оказаться лучше, чем ты думаешь :) Все эти особенности конкретной задачи — неявное приведение, перегрузка и т.д. — все должно транслироваться в ограничения. После этого сложность задачи будет выражаться только в:
— количестве переменных
— количестве допустимых значений (грубо говоря, типов)
— количеству ограничений.
Я понимаю, что этого добра будет очень много, т.к. я предлагаю формулировать ограничения на каждый чих. Но если посмотреть на соревнования CSP солверов, то картина не так уж плоха. Правда у них размер домена почему-то не превышает 26, но зато констрейнтов и переменных тысячи. Есть результаты и для больших доменов (при относительно небольшом числе переменных и констрейнтов)
В общем, надо экспериментировать, с CSP часто бывает так, что кажущаяся огромной задача для солвера оказывается элементарной, а кажущаяся простой — окажется неподъемной.
no fate but what we make
Re: Есть ли что-то о Constraint logic programming на Русском
От: LaPerouse  
Дата: 02.06.09 07:41
Оценка:
Здравствуйте, VladD2, Вы писали:

VD>Есть ли что почитать о Constraint logic programming и Constraint solver на Русском?


Curry поддерживает программирование в ограничениях.

Вот очень понятный пример.

Также см. http://www.informatik.uni-kiel.de/~mh/curry/examples/#CLP

PS Curry хорош тем, что это мультипарадигменный декларативный язык, не зацикливающийся на одной парадигме.
Социализм — это власть трудящихся и централизованная плановая экономика.
Re[2]: Есть ли что-то о Constraint logic programming на Русс
От: Курилка Россия http://kirya.narod.ru/
Дата: 02.06.09 07:46
Оценка:
Здравствуйте, LaPerouse, Вы писали:

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


И какие парадигмы кроме логического и функционального программирования там есть?
Re[3]: Есть ли что-то о Constraint logic programming на Русс
От: Аноним  
Дата: 02.06.09 08:46
Оценка:
Здравствуйте, Курилка, Вы писали:

К>Здравствуйте, LaPerouse, Вы писали:


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


К>И какие парадигмы кроме логического и функционального программирования там есть?


CLP

Итого — три парадигмы декларативного программирования.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.