Комментарии 42
И да, описываемое таки похоже на перл. Последствия, боюсь, будут аналогичными.
brother(X,Y):- parent(Z,X),parent(Z,Y),male(X),male(Y),X\=Y
это prolog
В этом отношении, английский синтаксис имеет более лучшие перспективы. Тем более даже в 1С имеются английские аналоги ключевых слов.
Декларативная же часть в общем повторяет возможности Пролога. Почему просто не реализовать библиотеку классов С++ реализующую синтаксис близкий к синтаксису Пролога через операции? Код мог бы выглядеть, скажем, так:
Symbol tom, tim, jake, janna, parent, male, female;
male(tom) <<= true;
male(tim) <<= true;
male(jake) <<= true;
female(janna) <<= true;
parent(tom, jake) <<= true;
parent(janna, jake) <<= true;
parent(tom, tim) <<= true;
Symbol brother;
Variable X, Y, Z;
brother(X, Y) <<= parent(Z, X) & parent(Z, Y) & male(X) & male(Y) & X != Y;
brother(X, Y)[0];
brother(X, Y)();
В последних двух строках предполагается запрос первого и всех решений соответственно. В отличие от предложенного вами языка, такой подход не требует значительных усилий по реализации — по сути транслятор сведён к нулю. При этом в программах с использованием такого подхода можно будет сочетать программирование на C++ и логическое программирование. Использование уже существующих библиотек C++ и их интеграция в такую модель логического программирования также упростится.
Это всё я к чему… А нужен ли вам новый язык?
Насколько я понял, все возможности императивной части сводятся к объявлению C++ функций, причём без типизации.Нет, не так. В императивной части можно писать какие угодно функции (для этого существую вставки кода на языке реализации). Вот только и вызывать их можно будет тоже только там.
Более того, какой смысл объявлять новые функции в рамках предложенного языка?Без типизации объявляются те функции, вызов которых возможен из декларативной части.
Реализация языка может быть произвольной (хоть на С++, хоть на Python), а декларативная часть будет унифицирована между любыми реализациями.
Декларативная же часть в общем повторяет возможности Пролога.Нет, возможности языка гораздо шире, чем возможности Пролога и позволяют решить основную проблему логических языков программирования, просто сейчас я на этом не заострял внимание. И основная фишка языка именно в этом, а не в его синтаксисе.
И как раз для этого и требуется определение функций и создание именно нового языка, а не просто надстройка над одним из существующих языков программирования.
Декларативная часть по сути и является предлагаемым вами языком, так как только она унифицирована между реализациями. Вам требуется для полного его раскрытия определение функций, но как я, надеюсь, показал, для этого не требуется делать настолько костыльную императивную часть в общем-то частью вашего языка. Вы ведь всё равно требуете от программиста знания языка реализации для использования императивного стиля. Вы пишите, что основная «фишка» вашего языка не в синтаксисе, а именно в модели вычислений, но при этом почему-то требуете создание нового языка под эту модель, хотя как я показал, продемонстрировать модель вычислений можно и путём расширения существующих языков — и этот путь требует меньших усилий. Я искренне не понимаю, почему для раскрытия основной «фишки» вашей модели вам так требуется создавать новый язык.
И если вас не затруднит, хотелось бы прочесть подробнее про то, как именно вы решаете проблему комбинаторного взрыва в предложенном языке.
Декларативная часть по сути и является предлагаемым вами языком, так как только она унифицирована между реализациями.И да и нет.
Да — именно на основе декларативной части реализуется основная задача языка, и Нет — так как императивный синтаксис нужен только бесшовного взаимодействия с существующими наработками и библиотеками.
В текущей статье я специально не стал акцентировать внимание на «модели вычислений», т.к. хотелось получить обратную связь немного по другой идее.
Но раз зашло дело об основном назначении языка, то его «фишка», это возможность автоматизации поиска решений на больших объемах данных. Дело в том, что в современных языках «функция» рассматривается как часть выполняемого кода, а мне было нужно на уровне синтаксиса отделить возможность декларации чистых функций как набора логических выражений без внешних зависимостей, что можно сделать только для полностью контролируемого синтаксиса.
Другими словами, чистые функции из декларативной части языка допускают горизонтальное масштабирование и замену прямого вычисления на вычисление с использованием нейросети.
А с использованием нейросети. Я вам уже приводил ссылку на статью Проблема логических языков программирования, в которой подробно раскрывается проблема и видимые пути её решения. И под «использованием нейросети» предполагается режим работы, когда вызов чистой функций верхнего уровня (с любой глубиной вложенности вызовов), заменяется на сгенерированную нейросеть с возможностью её обучения на фрагменте входных данных.
После такого обучения и сравнении с результатом, полученными на «прямых вычислениях», у нас может получить инструмент с возможностью автоматизации всего цикла решений задач с помощь нейросетей.
Исходя из этого спрошу более того: а не подменяется ли при этом сама задача? Там где от оригинала требовалось решить задачу корректно, в вашей модели решение будет получаться с некоторой вероятностью. Поэтому и проблема комбинаторного взрыва остаётся нерешённой. Вы считаете иначе?
т.е. вы предлагаете ускорить логическое программирование с использованием нейросетей?Да, причем с помощью инструмента, допускающим автоматизацию данного процесса.
Класс задачи и возможные пути её решения определяются разработчиком, а не самой программой, так же как и выбор более предпочтительной в использовании конфигурации нейросети. И использовать этот инструмент нужно только там, где такое решение допустимо, что опять же определяется человеком.
Автоматизации самого решения не происходит, просто относительно понятно работающее решение в рамках алгоритма перебора заменяется на непрозрачную нейросеть.… Вы считаете иначе?Да, считаю иначе. И это очень важно в тех случаях, где полный перебор вариантов за разумное время невозможен или требуется частное решение, но алгоритмическое решения отсутствует.
Что касается частного решения при отсутствии алгоритмического — само это частное решение обязано быть алгоритмическим. Иначе оно невозможно к исполнению на компьютере. Конечно алгоритм этого решения может быть записан в разных стилях, будь то императивный, логический, функциональный или иной стиль.
В любом случае предложение описывать нейросети в рамках логического программирования — интересное. Вы уже проверили его работоспособность? Ваш язык уже можно протестировать? Если нет — я вновь напомню, что для проверки работоспособности модели вычисления вполне может хватить расширения существующего языка. Та модель которую вы озвучили, как минимум в том виде как я её понял, выразима в качестве библиотеки. При этом вы быстрей довели бы модель до состояния, когда её можно протестировать и не было бы костыля в виде императивной части языка. Вновь спрошу — почему вам принципиально важно создание нового языка для выражения этой модели? Обычно сначала вырабатывается модель, а затем создаётся язык заточенный под неё — и не без причины — так просто проще. =)
Заранее прошу прощения, если язык уже реализован.
И вы не поверите, но я сперва и начинал с библиотеки. И именно в процессе её прототипирования и пришлось изменять саму концепцию и разрабатывать для этого отдельный язык, т.к. в случае библиотеки возникают две принципиально не решаемые проблемы:
1. Синтаксис описания данных все равно необходимо формализовывать. И это приходится делать или с помощью текстового описания и последующего парсинга или непосредственный кодинг. Если парсинг — то это уже сам по себе будет определенный синтаксис некого ограниченного языка. А если кодинг — получаем запредельная сложность ручной работы, т.е. то состояние, которое имеем сейчас.
2. Для использования библиотеки требуется знание языка программирования. И не начальные, а глубокие и специфические. Причем решение с помощью алгоритмических методов или с применением нейросетей имеют принципиально разные подходы и требуются совершено разной квалификации от разработчика.
По второму пункту я просто не вижу, как вам это мешает опробовать вашу модель вычислений в виде библиотеки. Насколько я понимаю, вы беспокоитесь заранее о некоторых пользователях, которых думаю пока что нет. Отмечу, что если реализовать библиотеку с использованием операций для моделирования синтаксиса близкого к предложенному, то трансляция станет тривиальной (так как операции языка переводятся в операции библиотеки непосредственно), и когда библиотека будет готова, транслятор можно будет написать без больших усилий. Вновь спрошу: чем вам так принципиально создавать новый язык, притом именно сейчас, когда модель вычислений не была ещё реализована даже в прототипе (насколько я понял)?
Я писал о реализации в библиотеке модели вычислений вашего языкаЯ тоже. Изначально я тестировал реализацию подхода именно в виде библиотеки и про отдельный язык и речи не было. Но через некоторое время вылезли проблемы, о которых я и написал в комментарии выше.
Напомню, что я предложил моделировать синтаксис языка через операции классов — где операции языка тривиально отображаются в них. Или это также запредельно сложно?Именно это и делает трансплайтер. Ведь нет универсального решения для всех классов задач. И модель вычислений как раз и должна описываться конечным пользователем системы, а не мной как разработчиков языка. Язык это просто инструмент для более легкого описания условий задачи.
И никакой новый язык вам не нужен, если вы и так можете запрограммировать, например, TensorFlow на С++ или в Python, ведь транслятор и будет делать ровно тоже самое — генерировать код на С++ для вызовов функций этих библиотек.
А использовать для этого новый язык или писать все вручную, это личный выбор каждого.
Я не предлагаю отказываться от трансплайтера, я предлагаю подготовить библиотеку на языке реализации, которая позволит на начальном этапе отказаться от трансплайтера, а впоследствии упростить его разработку.
Новые языки именно потому и нужны, что они позволяют проще программировать в рамках некоторой модели, позволяют думать о программе по-другому. Каждый язык поощеряет некоторые модели — так Хаскель об одном, Си о другом, Лисп о третьем, Пролог ещё об одном, Verilog и VHDL — вообще из другой оперы немного. Имея библиотеку в рамках которой вы можете проводить вычисления по своей модели уже проще создавать наиболее подходящий язык. Я описал путь возможной реализации такой библиотеки, который может упростить последующую разработку.
В любом случае удачи в этом проекте — жду следующей статьи, где, надеюсь, будет продемонстрирована работа программ такого стиля. =)
PS: а есть ли репозиторий с текущим кодом?
А репа сейчас закрыта от публичного доступа. Открою сразу, как заработают хотя бы пара примеров.
И спасибо!
Всё равно ведь выходит, что для программирования на предложенном языке человеку нужно уже знать C++.Это требуется только в том случае, если разработчик будет писать в императивном стиле. И кстати, это наверно самая большая проблема, за которую критикуют Пролог.
отсутствует проблема, присущая практически всем промышленным языкам — постоянное увеличение сложности синтаксиса языка из-за его естественного развития
Не так. Проблема переносится в те языки, которые включаются в список поддерживаемых для определения функций нового языка. Точнее — проблема остаётся в вызываемых языках, а новый язык делает вид, что проблемы нет. Но если проблемы нет у него, это не значит, что проблема отсутствует.
В целом поиск новых языков я считаю тупиком. Потому что главное для человека, это решение его проблем. А описаний проблем в том или ином виде уже предложено в огромном количестве. И никому миллионы видов описаний не помогли. Хотя перебор подходов к описанию выявил группы близких способов описания проблем, в этом был плюс развития языков на начальном этапе. В итоге пришли к императиву и декларативному определению описаний. К этому делению пытаются притянуть всякую функциональщину и прочие DSL-и, но это уже вкусовщина в чистом виде.
Рассмотрим императив и декларатив. Первое тривиально и особо рассматривать там нечего — задаёшь алгоритм на любом императивном языке и получаешь ошибку или решение. Недостаток простой — надо подробно задавать алгоритм. Во времена, когда библиотеки не были столь толстыми, как сегодня, подробности опускались до уровня машинных кодов, что несколько напрягало всех без исключения своими трудозатратами. Потом накопились библиотеки и худо-бедно базовые вещи можно «творить» относительно быстро.
Второе направление сразу предложило толстый слой алгоритмов в виде движков, которые работали с правилами, с таблицами, с графами и т.д. Поэтому у неопытных разработчиков возникало впечатление, что «теперь возможно всё», ведь вот оно, написал пару слов и получил нетривиальное решение. Но эти радости, разумеется, ограничены тем самым толстым слоем алгоритмов. Чего в толстом слое нет — то извольте написать ручками, и чаще всего — императивно. То есть недостаток всё тот же — трудозатраты. Но замазанные сверху толстым слоем занимательных алгоритмов.
И что же нам может дать очередной язык программирования в свете показанной выше картины? Как он снизит трудозатраты?
Сам по себе — никак. Всё, что можно было выжать из синтаксиса, давно из него выжато. Краткость описания (трудозатраты) снижены весьма прилично по сравнению с машинным кодом. Но необходимость в реализации требущихся для решения задач алгоритмов никуда не делась. На любом языке сегодня, по сути, мы просто обращаемся к существующей базе алгоритмов. Кому-то кажется, что такое обращение «очень умное», но это взгляд профана. Всё по прежнему на 100% императивно, хоть и с заметным сокращением усилий по написанию императива в части толстых библиотечных функций (включая декларатив). И где здесь место новому языку? Какими магическими закорючками можно заставить родиться новый алгоритм так, что бы это было быстрее, чем раньше, и что бы это не вело к большому количеству ошибок?
Ответ простой — все закорючки должны быть переведены в очередные библиотеки и выданы страждущим в виде готового к употреблению решения. То есть кто-то должен сесть и написать решение задачи за того, кто не хочет тратить время на её решение. Ну и по этой скользкой дорожке катистя весь бизнес — не делай ничего из того, что уже кем-то сделано. Правда в итоге выясняется, что всё сделанное другими (вот какая неожиданность!) было сделано без учёта потребностей данного бизнеса. Ну иприходится самим садиться и переписывать приличную часть чужого и бесплатного сыра.
Правда есть ещё одно направление — помощь IDE. Здесь я ожидаю определённый прогресс. Но важно понимать, что сам по себе язык, то есть его синтаксис, никак уже помочь не сможет. Либо вы пишете кучу библиотек плюс сложный плагин к IDE, либо ваш язык никому не нужен. В этом состоит суровая правда жизни. Замечаете разницу в указанном «либо-либо»? Она простая — вы даёте новые алгоритмы (бесплатно и много), либо вы не даёте. Если не даёте — вы никому не нужны. А что бы дать, нужно очень много попотеть. И ради чего потеть? Что бы очередные мамкины детки легко решали свои эгоистические задачки. Ну ладно, пусть будет ради удовлетворения своего стремления к прекрасному. Но повторюсь — вам это стремление встанет очень дорого. Многие человеко-годы на новые алгоритмы для библиотек и IDE. А синтаксис — ну зацепит несколько молодых и излишне восторженных детишек. Но не более. Наелись все уже сахара в этой области.
Так что могу лишь пожелать находить самоудовлетворение в производимом вами мысленном продкте. Иначе ведь вообще всё грустно станет.
Хотя есть ещё вариант «сообща». Но это совсем другая песня. Там много сильно отличающихся от привычных программистам проблем.
Здравствуйте, меня интересует тема и, возможно, мои (довольно поверхностные) наблюдения могут оказаться вам полезными.
Первое. Транспиляция в C (не в C++) может быть более гибким решением, когда вы будете добавлять понятие объекта. Семантика управления жизни объектов С++ — довольно специфическая, привязка к ней, с одной стороны, ограничит выбор возможностей, а, с другой стороны, повлияет на уровень сложности не в лучшую сторону. В качестве примера языка, старающегося избегать эти проблемы, приведу Nim. Его флагманский бэкенд — C (C++ бэкенд есть, но он альтернативен и, вероятно, не поддерживает недавно добавленные возможности Nim). В C++ деструктурирование объекта, даже уже перемещённого куда-либо, обязательно — поэтому перемещающие конструкторы, операторы присваивания должны заботиться об "опустошении" перемещённого объекта, чтобы при выполнении деструктора ничего не происходило. Такое (необязательное, если проектировать язык с ноля) усложнение специфично для C++, поскольку move-семантики не было в начальной версии языка, а когда она была введена позже, её нужно было "гладко" примирить с предшествовавшими правилами (диктующими обязательность вызова деструктора). В Nim, насколько я понимаю, эта лишняя сложность преодолевается: деструктор — лишь один из способов уничтожить объект, одна из потенциально нескольких "поглощающих" объект функций (хотя и наиболее стандартная и частоупотребляемая). Не знаю, как авторы Nim примеряют это c правилами C++ в C++-бэкенде языка (если оный не заброшен). Также, транспиляция в C кардинально упрощает проблемы FFI, бинарной совместимости раздельно скомпилированных библиотек. У C++ с этим — большие проблемы. Любопытное стороннее ответвление темы про объекты — в языке R более одной "системы объектов", запрограммированной поверх ядра языка в виде библиотеке.
Второе. В ваших примерах вы объединяете последовательное выполнение ("императивное") с бэктрекингом в духе Пролога ("декларативное" — под которым, на самом деле, в Прологе подразумевается более сложное императивное исполнение — с бэктрекингом; в частности, в Прологе порядок выражений, перечисленных в правой части через запятую, играет важную роль в плане производительности и вообще завершаемости исполнения "декларативной" программы). Обратите внимание на концепцию алгебраических эффектов (хорошо развитый пример — Koka, который, кстати, в новой версии тоже транспилируется в C), объединяющую под одим зонтом не только эти два аспекта, но и эффекты другого рода — исключения, завершаемость, асинхронность, недетерминизм, сторонние эффекты (state), асинхронное исполнение… (язык позволяет программировать новые эффекты кроме определённых в базовой библиотеке). У Koka — солидный математический фундамент: система алгебраических эффектов обладает свойствами, похожими на комбинирование монад в Haskell, при более компактных "церемониях", ложащихся на плечи программиста, использующего язык. Кстати, ещё один интересный аспект Koka — минималистический синтаксис, например, синтаксис while { тут условие } { тут действие } — это не встроенная конструкция языка с ключевым словом while, а вызов функции while, принимающий два параметра — анонимные функции. В Nim есть похожие синтаксические идеи, но Koka — более изящен в этом плане.
Третье — система гигиенических макросов может обеспечить минимальность ядра и богатый, знакомый "программистам на языке ..." синтаксис запрограммированный в базовой библиотеке поверх макросов. На ум приходит Racket (потомок Scheme, язык с богатыми средствами построения DSL) — к сожалению, для использования Racket нужно погрузиться в его лиспоподобный мир. С другой стороны, система гигиенических макросов регулярно появляется в современных языках (примеры: Nim, Rust, Julia) — это хороший способ сохранять ядро языка компактным, а богатство (выразительность) синтаксиса выносить в библиотеки.
Транспиляция в C (не в C++) может быть более гибким решением, когда вы будете добавлять понятие объекта...Решение действительно будет более гибким, но значительно возрастает трудоемкость реализации трансплайтера. Для реализации на С++ достаточно переопределить операции и генерируемый код уже можно компилировать.
А вот при реализации на С приходится делать тоже самое, только предварительно нужно еще реализовать сами операции. Я даже llvm пробовал прикрутить и вполне успешно, но в конечном итоге решил, что «лучшее враг хорошего». Ведь у меня сейчас нет цели, выбрать идеальный язык реализации. Возможно в будущем и можно будет попробовать другие бэкенды.
И по факту сейчас многие библиотеки и проекты используют именно С++ модель, а не чистый С (llvm, tensowflow), а они первые кандидаты на дальнейшее использование.
Кажется я отвечал, что пример на Прологе я взял просто как самый известный язык логического программирования. Но модели выполнения и их реализации у меня и в Прологе различаются принципиально.
Согласен с аргументом в пользу C++ бэкенда (про использование конкретных удобств, перегрузки операторов в вашем случае). Меня беспокоит нетривиальная модель жизненного цикла объектов в C++ — в случае, если C++ бэкенд на неё закладывается, она может протечь в семантику разрабатываемого языка, привнося риск искусственного усложнения. Если в генерируемый C++ коде не опирается на автоматический вызов деструкторов, жизнь проще… Однако, если, например, поддаться соблазну использовать хотя бы unique_ptr, вещи усложнятся.
В сторону от темы — вспомнил про появившийся на рубеже тысячелетий прологоподобный язык программирования Mercury, годами не вспоминал про него, а он, оказывается, жив! Ну, жив настолько, насколько бывает жив академический язык. Почитаю официальный сайт на досуге (меня интересует система типов в первую очередь).
Пока видна только темная комната, а имеется ли в ней черная кошка пока неясно.
То есть бинарный предикат представляется как пара объект/словарь.
А как планируется представлять предикаты большей арности?
В текущем представлении все возможные комбинации описаны в одном месте. Если связь один — к одному, то это представлено в виде множества объектов (словаря) с правой стороны.
Но если арность выше, то что справа — словарь словарей? Для всех возможных комбинаций?
Речь идет о свойставах объекта или о значениях свойств?
Если о свойствах объектов, то это одномерный словарь, у которого имена элементов обязательны.
Если о значениях свойств, то значением может быть в том числе и словарь у которого значением элемента так же можно быть словарь, т.е. размерность и вложенность не ограничена.
Вопрос был о втором. О значениях свойств.
Почему-то кажется, что такое представление сильно ограничивает возможности представления связей в сравнении с представлением в виде фактов.
Более того, не очевидны преимущества перед представлением подобного в императивном стиле. Т.е. в виде метода или свойства, возвращающего словарь.
То есть вся "магия" — в преобразовании (транспиляции) декларативной функции в специфическом синтаксисе — в обычную, выполняющую перебор?
И это очень важный момент, т.к. вы правильно заметили, данные в моем примере и факты в примере на Прологе записываются противоположным образом. На Прологе, как набор фактов, которые связываю разные объекты, а в моем случае, как набор свойств у одного объекта.
В общем случае представление данных зависит от точки зрения разработчика. Мне показалось, что такое представление будет выглядеть более логичным и особо подчеркивает возможность использования ООП подхода. Но нет ограничений на возможные представления данных, которые можно попробовать описать и в стиле фактов Пролога, к примеру элементов, одного списка.
Просто мне кажется, что такое представление это не очень выразительно, но большое спасибо за сам вопрос! Возможно в этом действительно что-то есть.
Что же касается «магии», то Пролог вынужденно использует именно такое представление, т.к. это требуется для его внутренних механизмов неявного поиска решения. Ведь он оперирует именно «фактами».
В моем случае все совсем по другому и механика поиска возможных вариантов явно задается с помощью итератора. А сам перебор реализуется за счет перебора значений объекта, которые и представлены итератором.
Если алгоритм решения задачи известен и достаточно императивной семантики, то данный язык использовать ненужно и проще взять тот же Python или С++.
Необычная концепция синтаксиса языка программирования