Архитектор
0,0
рейтинг
4 июля 2014 в 18:16

Разработка → Метапрограммирование (с примерами на JavaScript)

Эта статья, еще одна попытка переосмысления метапрограммирования, которые я периодически предпринимаю. Идея каждый раз уточняется, но в этот раз удалось подобрать достаточно простых и понятных примеров, которые одновременно очень компактны и иллюстративны, имеют реальное полезное применение и не тянут за собой библиотек и зависимостей. В момент публикации я буду докладывать эту тему на ОдессаJS, поэтому, статью можно использовать, как место для вопросов и комментариев к докладу. Формат статьи дает возможность более полно изложить материал, чем в докладе, слушатели которого, не освобождаются от прочтения.

Популярное понимание метапрограммирования обычно очень размытое, и чаще всего, заканчивается такими вариантами:
  • Шаблоны и макросы, используемые при компиляции
  • Программа, которая изменяет саму себя
  • Программа, генерирующая другую программу

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


Что такое моделирование?


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

Удивительно, но человек способен успешно решать задачи, сложность которых превышает возможности его памяти и мышления, при помощи построения моделей и абстракций. Точность этих моделей определяет их пользу для принятия решений и выработки управляющих воздействий. Модель всегда не точна и отображает только малую часть реальности, одну или несколько ее сторон или аспектов. Однако, в ограниченных условиях использования, модель может быть неотличимой от реального объекта предметной области. Есть физические, математические, имитационные и другие виды моделей, но нас будут интересовать, в первую очередь, информационные модели данных и модели программной логики. Парадигмы программирования, это и есть модели программной логики, например, императивное, декларативное, функциональное, событийное. Нашей же задачей, как программистов, можем обозначить не написание кода, а, в первую очередь, построение моделей и абстракций. Таким образом, метапрограммирование позволяет нам поднять уровень абстракции в моделях, что делает их универсальнее, а нашу работу значительно интереснее.


Что такое метапрограммирование?


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

Определения:
Метаданные — это данные, о данных. Например тип переменной, это метаданные переменной, а названия и типы параметров функции, это метаданные этой функции.
Интроспекция — механизм, позволяющий программе во время работы получать метаданные о структурах памяти, включая метаданные о переменных и функциях, типах и объектах, классах и прототипах.
Динамическое связывание (или позднее связывание) — это обращение к функции через идентификатор, который превращается в адрес вызова только на этапе исполнения.
Метамодель — модель высокого уровня абстрактности, из которой вынесены метаданные, и которая динамически порождает конкретную модель при получении метаданных.
Метапрограммирование — это парадигма программирования, построенная на программном изменении структуры и поведения программ.

Итак, нельзя начать применять метапрограммирование с сегодняшнего дня, но можно осознать, проанализировать и применять инструмент осознанно. Это парадоксально, но многие стремятся разделить данные и логику, используя фоннеймановскую архитектуру. Между тем, их следует не разделять, а правильным способом объединить. Есть и другие архитектуры, например, аналоговые решатели, цифровые сигнальные процессоры (DSP), программируемые логические интегральные схемы (ПЛИС), и другие. В этих архитектурах, вычисления производятся не императивно, то есть, не последовательностью операций обработки, заданной алгоритмом, а параллельно работающими цифровыми или аналоговыми элементами, в реальном времени реализующими множество математических и логических операций и имеющими уже готовый ответ в любой момент. Это аналоги реактивного и функционального программирования. В ПЛИС коммутация схем происходит при перепрограммировании, а в DSP императивная логика управляет мелкой перекоммутацией схем в реальном времени. Метапрограммирование возможно и для систем с неимперативной или гибридной логикой, например, я не вижу причины, чтобы одна ПЛИС не могла перепрограммировать другую.

Теперь рассмотрим обобщенную модель, показанную на схеме программного модуля. Каждый модуль обязательно имеет внешний интерфейс и программную логику. А такие компоненты, как конфигурация, состояние и постоянная память, могут как отсутствовать, так и играть основную роль. Модуль получает запросы от других модулей, через интерфейс и отвечают на них, обмениваясь данными в определенных протоколах. Модуль посылает запросы к интерфейсам других модулей из любого места своей программной логики, поэтому входящие связи объединены интерфейсом, а исходящие рассеяны по телу модуля. Модули входят в состав более крупных модулей и сами строятся из нескольких или многих подмодулей. Обобщенная модель подходит для модулей любого масштаба, начиная от функций и объектов, до процессов, серверов, кластеров и крупных информационных систем. При взаимодействии модулей, запросы и ответы — это данные, но они обязательно содержат метаданные, которые влияют на то, как модуль будет обрабатывать данные или как он указывает другому модулю обрабатывать данные.Обычно, набор метаданных ограничивается тем, что протокол обязательно требует для считывания структуры передаваемых данных. В двоичных форматах метаданных меньше, чем в синтаксических форматах, применяемых для сериализации данных (как, например, JSON и MIME). Информация о структуре двоичных форматов, по большей части находится у принимающего модуля в виде struct (структур для C, C++, C# и др. языках) или «зашита» в логику интерпретирующего модуля другим способом. Разделить, где заканчивается обработка данных с использованием метаданных и начинается метапрограммирование, достаточно сложно. Условно, можно определить такой критерий: когда метаданные не просто описывают структуры, а повышают абстракцию программного кода в модуле, интерпретирующем данные и метаданные, вот тут начинается метапрограммирование. Другими словами, когда происходит переход от модели, к метамодели. Основным признаком такого перехода, является расширение универсальности модуля, а не расширение универсальности протокола или формата данных. На схеме справа показано, как из данных выделяются метаданные и заходят в модуль, меняя его поведение при обработке данных. Таким образом, абстрактная метамодель, содержащаяся в модуле на этапе исполнения превращается в конкретную модель.

Прежде чем приступить к рассмотрению техник и приемов метапрограммирования, я бы хотел привести одну цитату, которую всегда привожу, если речь заходит о метапрограммировании. Она наталкивает на мысль, что метапрограммирование, это отражение такого фундаментального закона, на котором основаны вообще все кибернетические системы. То есть системы «живые», в которых происходит управление, коррекция поведения и параметров деятельности при помощи регулирования с обратной связью. Это позволяет системам воспроизводить свое состояние и структуру в разных условиях и с разными модификациями, сохраняя существенное и варьируя поведение, в том числе и порождая производные системы для этого.

«Вот что я имею в виду под производящим произведением или, как я называл его в прошлый раз, «opera operans». В философии существует различение между «natura naturata» и «natura naturans» – порожденная природа и порождающая природа. По аналогии можно было бы образовать – «cultura culturata» и «cultura culturans». Скажем, роман «В поисках утраченного времени» строится не как произведение, а как «cultura culturans» или «opera operans». Это и есть то, что у греков называлось Логосом.»
// Мераб Мамардашвили «Лекции по античной философии»

Как работает метапрограммирование?


Исходя из определения, нужно разобрать следующие три вопроса:
  1. Когда происходят изменения?
  2. Что именно изменяется?
  3. При помощи чего происходят изменения?

Когда происходят изменения: метапрограммирование времени разработки, это, например, когда IDE анализирует ваш код, как данные, помогая его модифицировать, подсказывая имена объектов и функций, их типы и даже генерирует шаблоны или автоматически строит блоки кода из схем или визуальных средств моделирования, например, в визуальных редакторах интерфейсов пользователя, баз данных и других CAD/CAM средств автоматизированной разработки. Примеры изменений времени компиляции: трансляторы, в том числе для создания типизированных алгоритмов из нетипизированных и для генерации кода из языка с более высоким уровнем абстракции в язык, исполняемый в конкретной среде, вплоть до ОС и аппаратной платформы. Но нас больше интересует изменение поведения программ во время их работы, это мы и рассмотрим подробнее ниже.

Итак, предлагаю следующую классификацию метапрограммирования по времени изменений поведения и структуры:
  • во время разработки (Design time)
  • во время компиляции (Compile time)
  • во время работы приложения (Run time)
    • во время выполнения задачи (Just-in-Time)
    • между задачами (Lazy)
      • по времени (Timer)
      • по внешнему вызову (Pull)
      • по событию (Push)
Производить интерпретацию и связывание Just-in-Time, не самый лучший способ, но иногда он единственный возможный, если метаданные приходят одновременно с данными. Но метаданные, все же, меняются реже, чем происходят запросы, поэтому модель можно строить заранее и кешировать ее в ожидании запросов и данных. Обновлять модель при конкретных вызовах, для минимизации обращений или можно периодически опрашивать источник хранения метаданных, на предмет их изменения. Лучше всего, конечно иметь канал уведомлений от источника, чтобы он инициировал обновление по принципу выталкивания (push).

Что именно изменяется?
  • типы данных и структуры данных;
  • идентификаторы (имена классов, типов, переменных, как внутри модуля, так и имена, по которым модуль обращается к другим модулям);
  • вызовы (имена функций и методов, динамическое связывание, в том числе с использованием паттерна stub/skeleton при котором, в одном модуле строится stub, представляющий объект, класс или функцию, находящуюся в адресном пространстве удаленного модуля, таким образом, чтобы все обращения к stub были идентичны удаленным обращениям);
  • параметры алгоритмов обработки данных или параметры моделей, которые могут варьироваться;
  • подстановка выражений, формул и логических выражений, регулярных выражений и т.д.;
  • динамически интерпретируется сам код (метаданные обычно декларативные, но это не обязательно, они могут быть императивными или могут содержать фрагменты императивного кода);
  • сериализация/десериализация данных, объектов и классов, а так же маршалинг с коррекцией кода при передаче из одного модуля в другой (обычно коррекцией адресов, но могут быть другие коррекции).

При помощи чего происходят изменения?
  • Парсинг и трансляция синтаксических структур (с разбором строковыми операциями или регулярными выражениями).
  • Доступ к идентификаторам по имени или индексу (в том числе к параметрам объектов, ассоциативным массивам и т.д.).
  • Полная интроспекция (см. определение выше, если понятие непривычное).
  • Индивидуация объектов первого класса (основной способ):
    • функций, через замыкания;
    • объектов, через динамическое создание и примеси;
    • всеми мыслимыми и немыслимыми средствами, которые предоставляет язык программирования.

Для чего нам нужно метапрограммирование?


Теперь мы можем выделить основные задачи и случаи, когда метапрограммирование существенно упрощает реализацию или вообще делает решение возможным:
  • Расширение функциональности, повышение универсальности ПО.
  • Динамические предметные области, когда изменения являются штатным режимом.
  • Упрощение межсистемной интеграции, это отдельная тема, но очень помогает.

Пример 1


Рассмотрим самый простой пример выделения метаданных из модели и построения метамодели (см. пример на github). Сначала определим задачу примера: есть массив строк, нужно отфильтровать их по определенным правилам: длина подходящих строк должна быть от 10 до 200 символов включительно, но исключая строки длиной от 50 до 65 символов; строка должна начинаться на «Mich» и не начинаться на «Abu»; строка должна содержать «V» и не содержать «Lev»; строка должна заканчиваться на «ov» и не должна заканчиваться на «iov». Определим данные для примера:

var names = [
	"Marcus Aurelius Antoninus Augustus",
	"Darth Vader",
	"Victor Michailovich Glushkov",
	"Gottfried Wilhelm von Leibniz",
	"Mao Zedong",
	"Vladimir Sergeevich Soloviov",
	"Ibn Arabi",
	"Lev Nikolayevich Tolstoy",
	"Muammar Muhammad Abu Minyar al-Gaddafi",
	"Rene Descartes",
	"Fyodor Mikhailovich Dostoyevsky",
	"Benedito de Espinosa"
];

Реализуем логику без метапрограммирования:
function filter(names) {
	var result = [], name;
	for (var i=0; i<names.length; i++) {
		name = names[i];
		if (
			name.length>=10 && name.length<=200 &&
			name.indexOf("Mich") > -1 &&
			name.indexOf("V") === 0 &&
			name.slice(-2) == "ov" &&
			!(
				name.length>=50 && name.length<=65 &&
				name.indexOf("Abu") > -1 &&
				name.indexOf("Lev") === 0 &&
				name.slice(-3) == "iov"
			)
		) result.push(name);
	}
	return result;
}

Выделяем метаданные из модели решения задачи и формируем их в отдельную структуру:
var conditions = {
	length: [10, 200],
	contains: "Mich",
	starts: "V",
	ends: "ov",
	not: {
		length: [50, 65],
		contains: "Abu",
		starts: "Lev",
		ends: "iov"
	}
};

Строим метамодель:
function filter(names, conditions) {
	var operations = {
		length: function(s,v) { return s.length>=v[0] && s.length<=v[1] },
		contains: function(s,v) { return s.indexOf(v) > -1 },
		starts:   function(s,v) { return s.indexOf(v) === 0 },
		ends:     function(s,v) { return s.slice(-v.length) == v },
		not:      function(s,v) { return !check(s,v) }
	};
	function check(s, conditions) {
		var valid = true;
		for (var key in conditions) valid &= operations[key](s, conditions[key]);
		return valid;
	}
	return names.filter(function(s) { return check(s, conditions); });
}

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

Пример 2


Второй пример мы будем сразу писать при помощи метапрограммирования (см. пример на github), потому, что если я представлю себе его размеры в размеры в говнокоде, то мне становится страшно. Описание задачи: нужно делать HTTP GET/POST запросы с определенных URLов или загружать данные из файлов и передавать полученные или считанные данные через HTTP PUT/POST на другие URLы и/или сохранять их в файлы. Таких операций будет несколько и их нужно производить с различными интервалами времени. Задачу можно описать в виде метаданных следующим образом:

var tasks= [
	{ interval:5000, get:"http://127.0.0.1/api/method1.json", expect:"OK", save:"file1.json" },
	{ interval:"8s", get:"http://127.0.0.1/api/method2.json", put:"http://127.0.0.1/api/method4.json", save:"file2.json" },
	{ interval:"7s", get:"http://127.0.0.1/api/method3.json", expect:"Done", post:"http://127.0.0.1/api/method5.json" },
	{ interval:"4s", load:"file1.json", expect:"OK", put:"http://127.0.0.1/api/method6.json" },
	{ interval:"9s", load:"file2.json", post:"http://127.0.0.1/api/method7.json", save:"file1.json" },
	{ interval:"3s", load:"file1.json", save:"file3.json" },
];

Решаем задачу при помощи метапрограммирования:
function iterate(tasks) {
	function closureTask(task) {
		return function () {
			console.dir(task);
			var source;
			if (task.get)  source = request.get(task.get);
			if (task.load) source = fs.createReadStream(task.load);
			if (task.save) source.pipe(fs.createWriteStream(task.save));
			if (task.post) source.pipe(request.post(task.post));
			if (task.put)  source.pipe(request.put(task.put));
		}
	};
	for (var i=0; i<tasks.length; i++) setInterval(closureTask(tasks[i]), duration(tasks[i].interval));
}

Видим, что мы написали «красивые столбики» и можно произвести еще одну свертку, вынеся метаданные уже внутри метамодели. Как будет выглядеть метамодель, конфигурируемая метаданными:
function iterate(tasks) {

	// Metamodel configuration metadata
	//
	var sources = {
		get:  request.get,
		load: fs.createReadStream
	};

	var destinations = {
		save: fs.createWriteStream,
		post: request.post,
		put:  request.put
	};

	// Metamodel logic
	//
	function closureTask(task) {
		return function () {
			console.dir(task);
			var verb, source, destination;
			for (key in sources) if (task[key]) source = sources[key](task[key]);
			for (key in destinations) if (task[key]) source.pipe(destinations[key](task[key]));
		}
	}

	for (var i=0; i<tasks.length; i++) setInterval(closureTask(tasks[i]), duration(tasks[i].interval));
}

Замечу, что в примере используются замыкания для индивидуации тасков.

Пример 3


Во втором примере используется функция duration, возвращающая значение в миллисекундах, которую мы не рассмотрели. Эта функция интерпретирует значение интервала, заданное как строка в формате:
"Dd h m s", например "1d 10h 7m 13s", каждый компонент которого опциональный, например "1d 25s", если функция получает число, то она его и отдает, это нужно для удобства задания метаданных, если мы задаем интервал напрямую в миллисекундах.

// Parse duration to seconds, example: duration("1d 10h 7m 13s") // Parse duration to seconds // Example: duration("1d 10h 7m 13s") // function duration(s) { var result = 0; if (typeof(s) == 'string') { var days = s.match(/(\d+)\s*d/), hours = s.match(/(\d+)\s*h/), minutes = s.match(/(\d+)\s*m/), seconds = s.match(/(\d+)\s*s/); if (days) result += parseInt(days[1])*86400; if (hours) result += parseInt(hours[1])*3600; if (minutes) result += parseInt(minutes[1])*60; if (seconds) result += parseInt(seconds[1]); result = result*1000; } if (typeof(s) == 'number') result = s; return result; }

Теперь реализуем интерпретацию, конфигурируемую метаданными:
function duration(s) {
	if (typeof(s) == 'number') return s;
	var units = {
		days:    { rx:/(\d+)\s*d/, mul:86400 },
		hours:   { rx:/(\d+)\s*h/, mul:3600 },
		minutes: { rx:/(\d+)\s*m/, mul:60 },
		seconds: { rx:/(\d+)\s*s/, mul:1 }
	};
	var result = 0, unit, match;
	if (typeof(s) == 'string') for (var key in units) {
		unit = units[key];
		match = s.match(unit.rx);
		if (match) result += parseInt(match[1])*unit.mul;
	}
	return result*1000;
}

Пример 4


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

var ds = wcl.AjaxDataSource({
	read:     { get:  "examples/person/read.json" },
	insert:   { post: "examples/person/insert.json" },
	update:   { post: "examples/person/update.json" },
	delete:   { post: "examples/person/delete.json" },
	find:     { post: "examples/person/find.json" },
	metadata: { post: "examples/person/metadata.json" }
});

ds.read({ id:5 }, function(err, data) {
	data.phone ="+0123456789";
	ds.update(data, function(err) {
		console.log('Data saved');
	});
});

Теперь проведем инициализацию из метаданных, получаемых из другого модуля и покажем, что прикладная логика не изменилась:
var ds = wcl.AjaxDataSource({
	introspect: { post: "examples/person/introspect.json" }
});

ds.read({ id:3 }, function(err, data) {
	data.phone ="+0123456789";
	ds.update(data, function(err) {
		console.log('Data saved');
	});
});

В слудующем примере мы создадим локальный источник данных с таким же интерфейсом, как и у удаленного и покажем, что прикладная логика так же не изменилась:
var ds = wcl.MemoryDataSource({ data: [
	{ id:1, name:"Person 1", phone:"+380501002011", emails:[ "person1@domain.com" ], age: 25 },
	{ id:2, name:"Person 2", phone:"+380501002022", emails:[ "person2@domain.com", "person2@domain2.com" ], address: { city: "Kiev", street:"Khreschatit", building: "26" } },
	{ id:3, name:"Person 3", phone:"+380501002033", emails:[ "person3@domain.com" ], tags: [ {tag:"tag1", color:"red"}, {tag:"tag2", color:"green"} ] },
]});

ds.read({ id:3 }, function(err, data) {
	data.phone ="+0123456789";
	ds.update(data, function(err) {
		console.log('Data saved');
	});
});

Выводы


Приемы метапрограммирования
  • Стиль описания задачи: декларативный (метаданные), использование императивных и функциональных вставок
  • Хеши (ассоциативные массивы) заранее не знаем ключ: var a = {}; a[key] = value;
  • Интерпретация строк, придумываем свои синтаксисы или берем общепринятые (json, js, regexp ...)
  • Примеси (mixins): заранее не знаем куда добавим function mixin(a) { a.fn=function(){ ... } }
  • Замыкания (closures): персонализируем функции fn = (function(a) { return function() { return a*2 } })(value)
Последствия метапрограммирования
  • Размер кода: чаще резко уменьшается, но иногда может немного увеличиваться
  • Быстродействие: незначительно снижаться, но при грамотной реализации остается примерно тем же
  • Гибкость: программный код становится более универсальным, сфера применения ПО расширяется
  • Интеграция: обычно значительно упрощается и требует меньше изменений кода
  • Удовольствие от работы: метапрограммировать интереснее поэтому удовольствия и мотивации больше
  • Скорость разработки: разрабатывать дольше, а поддерживать значительно проще, экономится уйма времени

Ссылки по теме


  1. Полные исходники примеров на Github: https://github.com/tshemsedinov/metaprogramming
  2. Слайды к докладу: http://www.slideshare.net/tshemsedinov/javascript-36636872

Старые статьи, чтобы проследить развитие идеи

  1. Метапрограммирование http://habrahabr.ru/post/137446/
  2. Динамическая интерпретация метамоделей http://habrahabr.ru/post/154891/
  3. Расширенная схема динамической интерпретации http://blog.meta-systems.com.ua/2011/01/blog-post_28.html
  4. Применение метамодели при проектировании баз данных с несколькими абстрактными слоями: Часть 1 http://habrahabr.ru/post/119317/
  5. Применение метамодели при проектировании баз данных с несколькими абстрактными слоями: Часть 1 http://habrahabr.ru/post/119885/
  6. Интеграция информационных систем http://habrahabr.ru/post/117468/
  7. Введение мета-уровеня http://blog.meta-systems.com.ua/2011/01/blog-post.html
  8. Метамодель в задачах интеграции информационных систем http://blog.meta-systems.com.ua/2010/07/blog-post.html
  9. Интеграция на уровне вызовов или мета-данных? http://blog.meta-systems.com.ua/2009/10/blog-post_18.html
  10. Модель и метамодель http://blog.meta-systems.com.ua/2009/10/blog-post_05.html
Пользуетесь ли Вы метапрограммированием?

Проголосовало 227 человек. Воздержалось 97 человек.

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

Timur Shemsedinov @MarcusAurelius
карма
165,0
рейтинг 0,0
Архитектор
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

Комментарии (23)

  • +2
    Хм. Забавно. Бывало использовал такое для разнообразной валидации и/или фильтров и всегда сомневался, правильно ли я делаю, не сильно ли все абстрактно и т.д. Ну теперь я спокоен, спасибо
  • +1
    Великолепно! И даже источники есть… Спасибо!!!
  • +2
    Очень хочется разобраться с программной архитектурой управления «руками и ногами» в живых системах. Подозреваю, там тоже метаподвох, заключенный в самоподобии иерархии управления. В звёздочки))
  • 0
    Извиняюсь, но зачем вы пишите код в одну строчку?

    for (var i=0; i<tasks.length; i++) setInterval(closureTask(tasks[i]), duration(tasks[i].interval));
    


    Как такое читать коллегам?
    • +1
      Ооо… Это далеко не худший вариант. Даже грех на такое коллегам жаловаться )))
    • +2
      Да забейте, идея же понятна. У него там масса неточностей в коде, например, в первом примере надо выкинуть флаг и остановиться (return) на первом же получившимся false, остальные условия бесполезно выполняться будут. Или parseInt надо использовать с системой счисления, иначе какой-нибудь parseInt(«010») вернёт «8».
    • +1
      JavaScript у меня тут не оптимальный, согласен, но я использую его тут как псевдокод.
    • –4
      Откройте для себя 140bytes )
  • +12
    Ну вот тема крутая, вроде автор действительно шарит, разложено все по полочкам, примеры красивые и интересные, но читать невыносимо скучно, и мне, как неподготовленному читателю, ничего не понятно

    1.
    Почему первый пример — метапрограммирование? Ведь все функции и опции запрограммированы заранее, что именно меняется в ран тайме?
    Становится ли это метапрограммированием от того, что мы вызываем функцию по ключу из хеша? (operations[key](s, conditions[key]);).

    Тогда каждый раз когда мы используем конструкцию something[key] это — метапрограммирование?
    А если бы мы захардкодили все те же функции в один гигантский switch, то это уже не метапрограммирование?

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

    3.
    Это метапрограммирование потому что используются регулярные выражения? И значения берутся из хеша? Если второе, то что, вообще любое использование хешей — метапрограммирование?

    4.
    Тут я вообще ничего не понял, есть какая-то библиотека, у которой есть несколько имплементаций одного интерфейса.
    Если в первом кусочке кода еще понятно, причем тут метапрограммирование: методы генерируются на основе конфигурации, то во втором и третьем вообще не понятно, причем тут метапрограммирование.

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

    5.

    Dependency injection в angular по ходу метапрограммирование, потому что имена переменных берутся из декларации функции, например:

    angular.module('hi', [])
          .value('someVar', 'hello')
    
    
    inject( function( someVar ){
      console.log(someVar); // 'hello'
    }
    

    6.
    Или матчеры в chai тоже должны подходить, потому что используют defineProperty.
    expect(something).to.be.ok
    

    7.

    Плагины в jquery добавляются в runtime, AMD и любая система модулей, любые операции с json, операции с хешами, замыкания, модификация прототипа и свойств объекта — все метапрограммирование?
    А что тогда не относится к мета программированию?
    • –1
      Метапрограммирование это, потому что вы как бы конфигурируете один раз написанный код. Вот надо вам в функции «duration» (см. примеры) миллисекунды добавить, ничего программировать (как в обычном программировании) не надо — достаточно добавить ещё одно свойство в объект.
      • +6
        Кто Вам сказал, что конфигурация кода — это метапрограммирование?
        • +1
          Я так понял автора статьи.
    • 0
      Так я же и говорю, что метапрограммирование повсеместно, это не что-то новое, а обобщение общепринятой практики изменения поведения кода при при помощи его конфигурирования метаданными в момент исполнения. Критерий разделения что называть метапрограммированием, а что не называть я тоже предлагаю — если в результате увеличивается абстрактность модели, и мы получаем более общую модель, т.е. метамодель, то это метапрограммирование.

      В первом примере есть замыкание на каждый conditions, что позволяет просто вводить новые условия и рекурсивный вызов при модификаторе not, этого достаточно для получения более общей модели. Да, пример прост, но тема сложная и если бы я привел тут код как генерируются 5000 сложнейших форм по метамодели в 80 строк, то за сложностью реализации мы бы не увидели сути — того, как метамодель во время запуска подражает модели на базе разных метаданных.
  • +12
    Очень сильно не хватает пункта «Пользуюсь метапрограммированием давно, но то, что описано в статье — бред собачий».

    Следим за руками.

    Метаданные — это данные, описывающие данные
    Метамодель — это модель, описывающая модели
    Метаисследование — исследование, имеющее объектом другие исследования

    Метапрограммирование — это парадигма программирования, построенная на программном изменении структуры и поведения программ.

    Хм, «меня терзают какие то смутные сомнения» (с)

    А почему бы не последовать за всем остальным миром и не пытаться переопределить существующие понятия (и метапонятия, хехе).
    Метапрограммирование — это программы, которые генерируют программы. ВСЕ!

    Скажем, старый добрый
    #define MIN(a, b) ((a) < (b) ? (a) : (b))


    Это метапрограммирование. Да, да, какой бы примитивной ни была сишная макросистема — она генерирует новый код в месте использования (который потом может быть а может и не быть «выброшен» оптимизатором) и поэтому является метапрограммированием.

    template <typename T> T min(T lhs, T rhs) {return lhs < rhs ? lhs : rhs; }

    Это тоже метапрограммирование. Для каждого типа порождается (инстанцируется) новая функция

    И даже уродство вроде
    $db->query("select * from users where name='" . $user . "'")

    Это, очевидным образом, тоже метапрограммирование.

    Логичнее всего метапрограммирование выражено в Scheme. Компиляция явным образом разделена на метауровни. Уровень meta-0 — это то, что, собственно, компилируется. Meta-1 — уровень который может генерировать код для meta-0 (собственно, макросы), meta-2 — уровень генерирующий код для meta-1 (макросы, генерирующие макросы). Количество метауровней стандартом не регламентируется.

    Ну а то, что описано в статье — это именно, что «бред собачий» и попытка переопределить существующую терминологию
    • +3
      Полностью согласен насчёт попытки переопределить терминологию, но что касается «бред собачий» — у него есть своё собственное имя: data-driven programming. Как вариант, описанный в статье подход может развиться в Domain Specific Language и дальше в Language Oriented Programming, которое уже относят к метапрограммированию.
      • +1
        Да, я таки переборщил. Подход в статье сам по себе имеет право жизнь, более того, конфигурирование фреймворков (хоть чужих хоть самописных) — зачастую действительно гораздо более поддерживаемый подход, чем клепание всего под конкретную задачу. Спасибо, что поправили — я полностью с Вами согласен.
        • –2
          Ну я же не говорю, что метапрограммирование это только то, что я показал в примерах, генерация кода не перестает быть метапрограммированием, но нет принципиальной разницы, при помощи чего меняется поведение программы, при перегенерации кода или при интерпретации, при замыкании метаданных с более абстрактной моделью или при использовании примесей или фабрик. Не использование хешей и замыканий делает из программирования метапрограммирование, а переход от модели к метамодели и от данных к метаданным.
          • 0
            Блин, то что показано в статье — это банальное программирование! Выделяем сущность и делаем к ней такие мостики/интерфейсы, что бы использовать ее в конкретных местах, listener/hadler и так далее. А люди поверят, что это метопрограммирование. Серьезно, лучше чем пудрить людям мозги тк с «не большой натяжкой» и С и assembler объектно-ориентированные, функциональные языки, напишите в начале материала: это статья полная отсебятина, «так как видит автор», «задумка творческая», «воспринимайте все критически» и «прошу на обсуждения в комментарии». А то ж ваш кисель в мозгах уже попал в 6150 мозгов :-) Чувствуете ответственность?
          • +1
            Не использование хешей и замыканий делает из программирования метапрограммирование, а переход от модели к метамодели и от данных к метаданным.
            Вот здесь и зарыта логическая ошибка в Ваших рассуждениях. Из перехода от приготовления еды для себя самого к приготовлению еды для всей семьи не следует что Вы из обычного человека стали профессиональным поваром.

            Программирование с использованием метаданных/метамодели — это всё ещё программирование с использованием метаданных/метамодели, а не метапрограммирование. Более того, я сомневаюсь, что можно выявить метаданные и/или метамодель в классическом примере метапрограммирования с макросом #define MIN(a,b).
            • –1
              Это не ошибка, это терминологический казус, я использую терминологию непривычную для Вас, потому, что в этой сфере терминология не устоялась, и написанное в википедии — не догма.
              • +1
                Написанное в википедии — не догма, более того это зачастую даже не правда, но, как правило, это общепринятое мнение. Поэтому в вопросах определения терминов, мне кажется, ссылаться на википедию вполне корректно.
  • +1
    The classic definition for a metaprogram is “a computer program that writes new computer programs.” [...] Another common definition for metaprogramming is “a computer program that manipulates other programs at runtime.”

    (Kevin Hazzard, Jason Bock, Metaprogramming in .NET)

    Функциональные вещи (типа замыканий и каррирования), программирование на основе модели и табличные методы — это не метапрограммирование.

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

    Этот тезис, кстати, ничем не подтвержден. Очень многие подходы к метапрограммированию, наоборот, усложняют поддержку — попробуй-ка найти все использования того или иного аспекта, или определить контракт, по которому он вбрасывается.
  • +3
    Ох, давно мне нигде не попадалась эта цитата, здесь, думаю, будет уместно:
    Любая достаточно сложная программа на Си или Фортране содержит заново написанную, неспецифицированную, глючную и медленную реализацию половины языка Common Lisp.

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.