пятница, 6 сентября 2013 г.

О Fluent-интерфейсах

В контексте недавно состоявшегося обсуждения (здесь, и ранее, здесь) у меня возникли основания порассуждать для себя об области применимости fluent-техники.
Изначально я воспринимал её исключительно утилитарно, как ещё один забавный способ построить иерархическую структуру объектов. Эдакое дерево.
Но достаточно быстро стало понятно, что техника подходит не для всего на свете.
Собственно, «крайние» мысли на этот счёт изложены ниже.
Собственно, вопрос. Не стоит ли смотреть на fluent-технику как на DSL?
Она выглядит как DSL и правила работы с ней напоминают правила работы с DSL.
Только DSL в контексте существующего языка.
Вероятно, это не просто увидеть, поскольку обычно DSL реализуется средствами другого языка.
fluent-техника - не синтаксический сахар в классическом понимании, поскольку не требует расширения синтаксиса языка программирования, в чём я вижу достоинство.

Fluent-техника действительно очень похожа на DSL как по реализации, так и по применению.
Поверхностно проверить моё утверждение можно относительно просто.
Выше я говорил, что вижу ограниченную область применения этой техники в Delphi и обозначил описание SQL - как пример задачи, где fluent-техника может принести пользу.
Учитывая, что SQL, в сущности, является DSL, можно посмотреть, получится ли использовать fluent-технику для описания объектами других известных DSL. Таких как XML, XPath, regexp, JSON и, не поверите, даже UML.
Понятное дело, что генерация UML-диаграммы в программном коде выглядит, по меньшей мере, странно. Но технически это возможно.
Для описания же XML, XPath, regexp и т.п. вполне можно найти достойные применения.
Что делает это возможным? Наличие у языка описания, что ограничивает возможное число вариантов в контексте построения fluent-цепочки.

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

Пример где это может оказаться востребованным.

Ряд СУБД (Oracle, Postgres, SQLite, Red Database (клон Firebird)), поддерживают полнотекстовый поиск, но при сходных возможностях, язык запросов в них заметно отличается.
Предположим, мы хотим:
  1. Обеспечить независимое от СУБД хранение полнотекстовых запросов.
  2. Предоставить пользователю возможность делать полнотекстовые запросы интерактивно и хотим его избавить от набора магических строчек вида «сверхпроводимость -"вики" -"яндекс" +"левитация" +"видео"», поскольку все эти плюсы-минусы и кавычки не интуитивны для неподготовленного пользователя.
    Нам потребуется создать интерфейс пользователя для формирования полнотекстового запроса. Вероятно, центральным элементом интерфейса будет древовидное представление запроса, поскольку в нём возможны скобки логические условия (AND, OR...).
Ввиду [1], вероятно, потребуется разработать свой диалект синтаксиса для полнотекстовых запросов, который будет при исполнении транслироваться в строку для полнотекстового запроса к используемой СУБД.
Ввиду [2] удобным способом описания языка будет набор соответствующих объектов.
Таким образом, для сохранения полнотекстового запроса, сформированного пользователем с использованием интерактивных средств будет сериализация построенной совокупности объектов.

Выше была преамбула. Теперь — амбула :-)
Как быть, если мы хотим построить запрос полнотекстового поиска в коде? Разумеется, отвлекаясь от специфики СУБД, в контексте которой он будет исполняться.
Очевидно, нам следует прибегнуть к нашему диалекту языка полнотекстовых запросов. По-простому — построить совокупность объектов.
Как это сделать проще всего? - Что если попробовать fluent-технику?
  // сверхпроводимость -"вики" -"яндекс" +"левитация" +"видео"
  full_text_req := NewFullTextRequest()
    .AND_Bracket([
      FullText_Words(['сверхпроводимость']),
      FullText_Minus(['вики', 'яндекс']),
      FullText_Plus(['левитация', 'видео'])
    ]);

Что же здесь общего с DSL? - Ну, кое-что есть :-)
Например, проверку синтаксиса нашего «диалекта» языка запросов мы возлагаем на компилятор Delphi.
Так, NewFullTextRequest возвращает интерфейс IFullTextRequest, содержащий методы AND_Bracket и OR_Bracket – скобки для условий поиска, элементы которых объединены условиями, соответственно, AND или OR.
Подобно этому, AND/OR_Bracket принимают в качестве параметра массив элементов типа IFullTextCondition, которые умеют создавать функции FullText_Words/Minus/Plus, которые, в свою очередь, принимают на вход массивы строк, посредством которых описываются условия.
Далее, если потребуется расширить наш язык новыми понятиями, мы можем следовать парадигме: определить место в существующей структуре, в которой востребованы новые понятия и поддержать соответствующие fluent-методы.
Например: новый вид условий XOR можно поддержать «рядом» с AND/OR_Bracket.

Отвечу на один вопрос из множества, которые могут появиться.
[Q] Зачем «городить огород» с описанием диалекта посредством объектов? Почему не парсить строчку, оговорив предварительно её формат? Т.е. сформулировав диалект.
[A] Например, по следующим причинам:
  1. При изменениях в языке, запросы, описанные в коде в виде строчек приведут к ошибкам при выполнении, а записанные во fluent-технике к ошибкам компиляции.
  2. Работать с объектами проще, чем со строками, поскольку пропадает потребность в разборе строк. А обрабатывать запрос, сформулированный в нашем диалекте придётся перед его выполнением средствами используемой СУБД.
  3. Объекты, вероятно, всё равно потребуются ввиду [2] технических требований. Поэтому, строки можно рассматривать как формат сериализации этих объектов, но если из технических соображений сделать объекты потомками TComponent, «из коробки» станет доступна и сериализация.
 На этом, думаю, пока следует остановиться...

11 комментариев :

  1. Сейчас уже поздно и могу тупить, но причём тут DSL? Fluent-интерфейс это обычный паттерн программирования, который описывает правила построения объектов и их использования.
    P.S. Блин, иногда расшифровываю DSL как DamnSmallLinux :( Видимо, это уже деформация :)

    ОтветитьУдалить
  2. «P.S. Блин, иногда расшифровываю DSL как DamnSmallLinux :( Видимо, это уже деформация :)»
    -- Разумеется, в моём сообщении под DSL понимается Domain-Specific Language.
    Подробно об этом здесь: http://ru.wikipedia.org/wiki/Предметно-ориентированный_язык_программирования

    «Fluent-интерфейс это обычный паттерн программирования, который описывает правила построения объектов и их использования.»
    -- Краткий ответ я привожу в сообщении.
    Совсем кратко: fluent-интерфейсы можно рассматривать как спецификации специализированного языка, а использование fluent-техники (т.е. построение и настройку объектов с её использованием) - как создание программ на этом языке.
    Кроме того, мне показалось, что об использовании текучих интерфейсов в контексте DSL говорит и Мартин Фаулер вот здесь: http://martinfowler.com/bliki/FluentInterface.html.
    Более того, обновив свой пост в 2008 году он сообщил, что раскрывает тему в своей новой книге: http://martinfowler.com/books/dsl.html.
    Кстати, неплохая вроде, книжка. Жалко только на русский не перевели. Думаю, не нужно говорить, где её можно почитать... ;-)

    ОтветитьУдалить
  3. Отличная статья с точки зрения структуры представления и стилистики описания. Очевидно, автор - человек с большим опытом освещения сложных (местами концептуальных) вопросов. Но красота и лёгкость не должна уводить нас от сути!

    >>1.При изменениях в языке, запросы, описанные в коде в виде строчек приведут к ошибкам при выполнении, а записанные во fluent-технике к ошибкам компиляции.

    К сожалению, на приведённом примере указанная мысль не является в достаточной мере проиллюстрированной. Путём догадок можно понять, что в
    >>// сверхпроводимость -"вики" -"яндекс" +"левитация" +"видео"
    Изменится могут лишь "плюсики" и "минусики", а также "действие по умолчанию" с данной строкой, но - синтаксически, а не семантически. Тогда - зачем?

    Да и более высокого порядка значимости проблема - изменения языка. Вполне вероятно, что "по уму" изменения языка должны давать над-множество, обеспечивая совместимость с "старым" синтаксисом. Если это не так, то это - нецелесообразно. Если на этапе "строительства" языка - то - возможно. Что, однако, позволяет говорить о возможности и, даже, необходимости конечного построения DSL.

    >>1.При изменениях в языке, запросы, описанные в коде в виде строчек приведут к ошибкам при выполнении, а записанные во fluent-технике к ошибкам компиляции.
    >>Например, проверку синтаксиса нашего «диалекта» языка запросов мы возлагаем на компилятор Delphi.

    Опять же, является ли нашей задачей смещение центра вычислительной тяжести с runtime на design-time (или coding-time?). А зачем? Просто избавление разработчиков от необходимости реализации "своего DSL"?
    Ценно в разрезе помочь заказчику снизить стоимость проекта. Нет?

    >>Вероятно, центральным элементом интерфейса будет древовидное представление запроса, >>поскольку в нём возможны скобки логические условия (AND, OR...).

    Запрос с естественного языка с привлечением технологий ИИ будет оптимальным, если мы заботимся об удобстве пользователя. Заполнение некого поискового "формуляра" пахнет книжной молью и пыльными полками библиотек в до-электронную эпоху.
    Хотя и тогда были "интеллектуальные поисковики".
    - Девушка, что посоветуете почитать на тему левитации в художественном ключе? Да-да, имею ввиду фантастику!

    ОтветитьУдалить
    Ответы
    1. «К сожалению, на приведённом примере указанная мысль не является в достаточной мере проиллюстрированной. Путём догадок можно понять, что в
      // сверхпроводимость -"вики" -"яндекс" +"левитация" +"видео"
      Изменится могут лишь "плюсики" и "минусики", а также "действие по умолчанию" с данной строкой»
      – Мне казалось, что я указал на область, в контексте которой приводил свой пример.
      Это языки запросов для полнотекстового поиска, которые поддерживаются разными СУБД.
      Подробности можно получить по ссылкам:
      Apache Lucene: http://lucene.apache.org/core/4_4_0/queryparser/org/apache/lucene/queryparser/classic/package-summary.html#package_description
      SQLite: http://www.sqlite.org/fts3.html
      Oracle: http://docs.oracle.com/cd/B28359_01/text.111/b28304/cqoper.htm#CHDEGDDF
      PostgeSQL: http://www.postgresql.org/docs/8.3/static/textsearch.html
      Как легко убедиться, там всё не сводится только к «плюсам» и «минусам», на которых я решил остановиться только для простоты, чтобы проиллюстрировать саму идею.

      «но - синтаксически, а не семантически.»
      – Не убеждён, что понял Вас здесь.

      «Тогда - зачем?»
      Затем, чтобы упростить построение конструкций целевого DSL (язык полнотекстовых запросов в моём примере) в программном коде с одной стороны, и обеспечить проверку создаваемой конструкции со стороны компилятора Delphi – с другой.

      «Да и более высокого порядка значимости проблема - изменения языка. Вполне вероятно, что "по уму" изменения языка должны давать над-множество, обеспечивая совместимость с "старым" синтаксисом. Если это не так, то это - нецелесообразно.»
      – Опять же, не убеждён, что понял Вас правильно, поэтому буду отвечать на то, как я понял.
      Обеспечение совместимости со старым кодом имеет свой ресурс. При существенных изменениях целесообразнее адаптировать существующий код, чем обеспечивать совместимость, ведущую к утяжелению языка, эклектике и всевозможным недоразумениям.
      IMHO концептуальная чистота языка очень важна, соответствующую тему я затрагиваю в соседних постах.
      Если языком (DSL) являются интерфейсы (в смысле соответствующих конструкций Delphi) — значительно проще исправить ошибки компиляции, чем «отлавливать» их затем у клиента.

      «Если на этапе "строительства" языка - то - возможно. Что, однако, позволяет говорить о возможности и, даже, необходимости конечного построения DSL.»
      – Что «возможно», Всеволод? Я уже совсем потерял нить Ваших рассуждений.
      Вы говорите «вообще» или о приведённом мною примере?
      В моём примере DSL уже есть (их даже несколько, по количеству ссылок, которые я привёл выше) — это разные диалекты языка запросов для полнотекстового поиска.
      Вопрос в том, как, абстрагируясь от синтаксических отличий обеспечить применение языка полнотекстовых запросов в программном коде.
      В этом смысле применение текучих интерфейсов для меня выглядит весьма привлекательно: и коротко, и ёмко, и с контролем на этапе компиляции.

      Удалить
    2. «Например, проверку синтаксиса нашего «диалекта» языка запросов мы возлагаем на компилятор Delphi.
      – Опять же, является ли нашей задачей смещение центра вычислительной тяжести с runtime на design-time (или coding-time?). А зачем?»
      – Ну, например, по тем же причинам, вследствие которых появилась статическая типизация.
      Проще исправить ошибки компиляции, чем «вылавливать» их при исполнении программы.

      «Просто избавление разработчиков от необходимости реализации "своего DSL"?»
      – Избавление от необходимости реализации «своего DSL» это следствие, а не причина.
      Зачем разрабатывать компилятор и исполняющую систему «своего DSL», если при использовании текучих интерфейсов в качестве первого мы применим компилятор Delphi, а в качестве второго будет выступать построенная с помощью fluent-техники cтруктура объектов плюс, возможно, алгоритм, обрабатывающий эту структуру.

      «Ценно в разрезе помочь заказчику снизить стоимость проекта. Нет?»
      – Совершенно с Вами согласен. Я вижу снижение стоимости для заказчика в том, что не придётся делать ненужную работу — разрабатывать поддержку «своего DSL», вместо этого уделив время проработке соответствующих текучих интерфейсов.
      Да, вероятно, это сработает не всегда и не везде. Но во многих случаях — сработает и выйдет существенно дешевле.

      «Вероятно, центральным элементом интерфейса будет древовидное представление запроса, поскольку в нём возможны скобки логические условия (AND, OR...).
      – Запрос с естественного языка с привлечением технологий ИИ будет оптимальным, если мы заботимся об удобстве пользователя.»
      – Не убеждён, что ИИ корректно распознает всё, что может прийти в голову гражданину... :-)

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

      «Хотя и тогда были "интеллектуальные поисковики".
      - Девушка, что посоветуете почитать на тему левитации в художественном ключе? Да-да, имею ввиду фантастику!»
      – Сейчас можно обойтись и без девушки:
      {quote}
      > Выбрать записи, для которых [все условия выполнены]
      |-- 1. [Поле] [Жанр] [равно] [значению] [1024 – Фэнтези]
      |-- 2. [Поле] [Текст] [содержит текст] [левитация]
      {/quote}
      Квадратными скобками отмечены фрагменты интерактивного запроса, которые могут быть изменены.
      Предполагается, что по содержимому книги построен полнотекстовый индекс.

      Удалить
  4. Спасибо! Очень хорошо пояснили мысли в статье. Можно сказать, отлично дополнили! По большинству вопросов я на 80% стал разделять Вашу точку зрения.
    Ну разве что про использование компилятора Delphi. Но это уже вопрос стратегии преоктирования архитектуры/функционала/компонентного состава, что вне контекста конкретного проекта не даст нам однозначно-приемлемого всеми решения.

    >>я предлагал «использовать графическую нотацию для описания запроса пользователем»... :-)

    А вот это - свежо! Реально неожиданный поворот темы!
    На самом деле разрабатывал "экспертную систему" для ... скажем так, организации, которая по материальным следам может воссоздать изначальную картину события. Ну Вы поняли :)

    Там РЕАЛЬНО большая проблема с формированием поискового запроса. Графическая нотация - это сильно! Жаль, что данный проект уже вне сферы моей текущей занятости. Но концептуально это свежее дыхание. Графическая нотация для описания запросов... Думаю, Александру Люлину это бы понравилось, как апологету UML :)

    Если придумаете - опубликуйте! Обязательно изучу. Удачи в этом!

    ОтветитьУдалить
  5. «Ну разве что про использование компилятора Delphi. Но это уже вопрос стратегии преоктирования архитектуры/функционала/компонентного состава, что вне контекста конкретного проекта не даст нам однозначно-приемлемого всеми решения.»
    -- Мне было бы очень интересно услышать Ваши соображения на этот счёт.
    Моя позиция такова (чем больше мы обсуждаем, тем лучше формируется представление о предмете обсуждения, по крайней мере - у меня).
    Уже существует некоторый DSL (SQL, язык полнотекстовых запросов и т.д.). Нужно найти удобную форму отображения этого языка в программный код на компилирующем языке программирования. Например, для того, чтобы сформировать структуру SQL-запроса в *коде* приложения, не прибегая к хранению SQL в константах или текстовых ресурсах.
    Это может потребоваться для того, чтобы отвлечься при описании запроса от специфики SQL СУБД (например, в Oracle вместо LEFT/RIGH JOIN принято писать "+", есть разные способы оформления имён полей, ну и т.д.).
    Предположим, что вместо использования текста мы создали классы, описывающие синтаксические конструкции SQL (тем более, что это нетрудно обеспечить). Как же построить необходимую структуру в коде?
    Создавать объекты вручную по элементов управления VCL на форме - слишком громоздко. Это не эргономично, кроме того, за "множеством деревьев" легко потерять "лес" - т.е. структуру описываемого SQL.
    Но можно ведь поступить проще - использовать текучие интерфейсы.

    Что мы выигрываем:
    1. мы можем, например, имена полей и таблиц хранить в константах и указывать их при вызове fluent-функций. Это не просто часть текста, наличие константы проверит компилятор.
    2. каждая SQL-конструкция (FROM, JOIN, WHERE и условия) описывается соответствующим интерфейсом с методами - согласитесь, компилятор Вас защитит от "FORM" вместо "FROM".
    3. структура вызовов будет напоминать тот DSL, который мы описываем посредством fluent-техники (посмотрите примеры: http://www.querydsl.com/)

    Что мы проигрываем:
    Fluent-техника, в идеале, "свой" DSL. Со всеми вытекающими из этого последствиями.
    Вероятно, нам придётся создать специальные классы для того, чтобы обеспечить возможность использования fluent-техники для заданного набора объектов. Такие классы будут представлять "сам язык". Обработка построенных по ним объектов (в fluent-стиле) даст нам целевой набор объектов (который будет соответствовать результатам компиляции DSL), которые можно далее преобразовать в SQL.
    Не исключено, что нам придётся использовать паттерн Builder: http://nesteruk.wordpress.com/2010/08/25/fluent-builder-in-csharp/
    Т.е. выходит совершенно не бесплатно, что означает, что далеко не для любой задачи плюсы перевесят минусы.
    Но рискну предположить, что для SQL и для полнотекстовых запросов - точно перевесят.

    ОтветитьУдалить
  6. «я предлагал «использовать графическую нотацию для описания запроса пользователем»... :-)
    -- А вот это - свежо! Реально неожиданный поворот темы!»
    -- Вообще-то, я не сказал ничего нового. Интерактивное представление SQL на выборку существует уже давно. Правда существует в таком виде, что для конечного пользователя не годятся.
    Но можно же разработать более адекватное представление, которое и будет тем "интерактивным DSL" для пользователя.
    У нас такое представление есть. Очень упрощённо - это дерево условий, в которых возможно указание межтабличных операций.
    Простой пример я привёл выше для поиска книг в жанре «Фэнтези», в которых упоминается «левитация».

    «На самом деле разрабатывал "экспертную систему" для ... скажем так, организации, которая по материальным следам может воссоздать изначальную картину события. Ну Вы поняли :)»
    – Да, я понял. Но это сильно другая задача.
    IMHO вообще тема языка запросов к экспертным системам проработана довольно слабо, в отличие от, скажем, языка запросов к РСУБД. В частности, в классическом понимании, в качестве языка запросов ЭС мог быть использован естественный язык, который вообще, с крайним трудом поддаётся формализации. Или, как альтернатива, Prolog, который чужд почти любому пользователю. Всё это не способствует тому, чтобы определиться с набором понятий, в которых будет формулироваться запрос к БЗ. Соответственно, без формализации или формализации на языке, далёком от пользователя, ценность инструмента оказывается под сомнением.
    Вообще же, у меня сложилось впечатление, что интерес к теме экспертных систем существенно поугас с появлением GUI-интерфейсов. Но я не специалист в этой области, просто интересно текущее положение дел в ней.

    ОтветитьУдалить
  7. Отбросим "обиды".. http://programmingmindstream.blogspot.ru/2013/12/fluidquery.html

    ОтветитьУдалить
  8. Да нет никаких обид Александр :-) Вообще никаких. Но нет и времени, катастрофически... Поэтому, в настоящий момент, я только читатель.
    Относительно Вашей ссылки - да, это простой и естественный, на мой взгляд - намного более последовательный аналог LINQ в Delphi.

    ОтветитьУдалить
  9. Про LINQ я до сегодняшнего дня - НЕ ЗНАЛ. Спасибо!

    ОтветитьУдалить