Статьи

Декларативное программирование

Я знаю, что некоторые люди клянутся декларативным программированием. Им нравятся идеи, стоящие за  муравьем  (и  make ),  SCons  и связанные с ними примеры.

Вы можете гуглить «муравей v. Maven v. Gradle», где люди жалуются, что является более декларативным. Смысл нытья в том, что более декларативно == хорошо, а любые следы процедурного или императивного программирования == плохо.

Все, конечно, без какого-либо действительно хорошего обоснования того, почему декларативный лучше. Предполагается, что декларативный просто имеет неисчислимые преимущества. И да, я начал с  http://en.wikipedia.org/wiki/Declarative_programming . Проблема не просто спорная; обоснование слабое.

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

Или, может быть, декларативный имеет ограниченную полезность.

Там. Я сказал это. Ограниченная полезность.

Я думаю, что функциональный подход может быть лучше, быстрее и проще.

Боковая панель Ranting

Код ниже. Вы можете перейти к разделу «Функциональная система сборки » и не пропустить много.

Декларативное программирование кажется применимым к случаям, когда порядок операций может быть легко определен. Кажется, что значительная ценность декларативного программирования состоит в том, чтобы полагаться на оптимизирующий компилятор, переставляющий декларации в правильно упорядоченные императивные шаги. С этой точки зрения кажется, что ant / maven / gradle — это оптимизаторы, которые смотрят на зависимости между функциями преобразования и затем применяют функции в правильном порядке.

Похоже, мы пишем такие выражения:

x.class = java(x.java)
xyz.jar = jar(x.class, y.class, z.class, ... )
app.war = war(xyz.jar, abc.jar, ... )

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

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

Это  потенциал Разница в том, что большинство людей, которые имеют дело с муравьем / мавеном / грейдлом, склонны расставлять вещи в более или менее правильном порядке, чтобы другие могли понять, что, черт возьми, происходит. В тривиальных случаях, когда мы создаем простые веб-сайты, правила по умолчанию развивались до такой степени, что они работают практически во всех случаях, поэтому мы даже не смотрим на конфигурацию инструментов. Мы нажимаем Ctrl + B, зная, что все настроено правильно.

Некоторые требования Некоторые

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

Это не очень сложно. Класс для определения зависимости. Набор плагин стратегий. Некоторые статические определения фактических правил. Был там. Сделано это.

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

Другой конец спектра — это отдельные шаги, которые все вручную координируются с помощью такого инструмента, как BMC Control-M. Это требует бесконечного ручного вмешательства, чтобы убедиться, что все различные задачи правильно определены в Control-M.

Где-то посередине находится настраиваемое приложение с некоторыми правилами обработки, чтобы придать ему гибкость. Но некоторая определенная структура избавляет от необходимости тщательно спланированного ручного вмешательства и глубокого опыта.

Функциональная система сборки

Мы можем изобразить муравьиную систему сборки, определенную функционально.

Ядро — это функция, которая реализует правила сборки, если это необходимо:

def build_if_needed( builder, target_file, *source ):
    if target_ok( target_file, *source ):
        return "ok({0},...)".format(target_file)
    builder( target_file, *source )
    return "{0}({1},...)".format(builder.__class__.__name__,target_file)

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

Это зависит от некоторых вспомогательных функций: target_ok () проверяет время модификации файлов. Различные строители выполняют различные операции, необходимые для создания одного из источников.

Вот функция target_ok ()

def target_ok( target_file, *source_list, logger=logging ):
    try:
        mtime_target= datetime.datetime.fromtimestamp(
            os.path.getmtime( target_file ) )
    except Exception:
        return False
    # If a source doesn't exist, we throw an exception.
    times = (datetime.datetime.fromtimestamp(
            os.path.getmtime( source ) ) for source in source_list)
    return all(mtime_target > mtime_source for mtime_source in times)

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

У строителей сложные функции. Им необходимо объединить subprocess.check_call () с функцией, которая создает команду. Мы можем сделать функциональную композицию несколькими способами в Python: мы можем комбинировать функции через декораторы. Мы также можем комбинировать функции через Callables. Мы могли бы написать функцию более высокого порядка, которая комбинирует check_call () с функцией для создания команды.

Мы выберем функцию высшего порядка и создадим частично оцененные формы, используя functools.partial ().

Вот типичный случай:

def subprocess_builder( make_command, target_file, *source_list ):
    command= make_command( target_file, *source_list )
    subprocess.check_call( command )

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

def command_rst2html( output, *input ):
        return ["rst2html.py", "--syntax-highlight=long", "--input-encoding=utf-8", input[0], output]

rst2html= partial( subprocess_builder, command_rst2html )

Эта функция rst2html () может быть использована для определения правила зависимости. У нас может быть что-то вроде этого:

files_txt = glob.glob( "*.txt" )
    for f in files_txt:
        build_if_needed( rst2html, ext_to(f,'.html'), f )

Это правило указывает, что файлы * .html зависят от файлов * .txt; при необходимости используйте функцию therst2html () для создания необходимого html-файла, если txt-файл новее.

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

def ext_to( filename, new_ext ):
    name, ext = os.path.splitext( filename )
    return name + new_ext

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

Отдельные настройки — это команды сборки, такие как rst2html (), где мы создали список строк командной строки для subprocess.check_call (). Мы также можем легко создавать функции, которые полностью выполняются в процессе, или функции, которые объединяют работу для разделения процессов через очереди или веб-службы RESTful.

Итоги

Похоже, что декларативное программирование не очень полезно. Может быть, есть ниша, но мне кажется, что это небольшая ниша.

Я уверен, что объектно-ориентированный подход к этой проблеме не лучше. Я написал эту потертую версию, и она стала больше. Там просто больше кода, и не очень понятно, что происходит. Наследование может быть трудно выяснить.

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