Самый бесполезный шаблон

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

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

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

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

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

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

Конструкцию микшина мы решили спроектировать на базе того факта, что макрос todo! в Rust — это просто заглушка для функций, которая автоматически выводит тип возвращаемого результата и создает нечто, что по умолчанию, обладает таким типом.

В D для определения того, что возвращает функция или метод есть замечательная конструкция typeof(return) и которая позволяет вычислить тип того, что выйдет из функции, и присвоить этот тип некой переменной или выражению. Такая конструкция могла бы заменить весь макрос todo!, но в некоторых случаях, она не поможет — поскольку нужно определить не только тип, но и значение, которое ему соответствует. Для таких целей, в D также предусмотрена отличная вещь — свойство .init, у типов, которое позволяет вернуть значение по умолчанию для некоторого типа. Совместив две упомянутых ранее идеи, а также то, что результирующая конструкция в целом делать во время выполнения ничего не должна, то используя средства времени компиляции можно придти к следующему шаблону:

template todo()
{
	const char[] todo = `
	static if (is(typeof(return) : void))
		return;
	else
		return typeof(return).init;
	`;
}

Это обычное применение эпонимичного шаблона (т.е. такого, в котором внутренняя переменная совпадает с именем шаблона и является его результатом), однако, тут есть одна вещь, про которую мы ничего не упоминали. Это условие в static if. Данное условие является страховкой от того, что шаблон будет использоваться в тех функциях или выражениях, которые ничего нее возвращают, т.е. проще говоря, в тех случаях когда на выходе только void. Выражение is(T : S) проверяет является ли тип T приводимым к S, что в применении к нашему случаю, означает, проверку на то, что функция возвращает void. Такая страховка нужна, поскольку для типа voidнет свойства .init, и это помогает применять данный шаблон и для таких случаев (что является избыточным, однако, почему бы и нет?). Оборачивание внутренностей шаблона в static if не только позволит провести весь набор операций с определением типа на стадии компиляции, но и создаст первоклассную оптимизированную заглушку для проверок со стороны компилятора.

С одной стороны, такой простой шаблон абсолютно бесполезная штука — мы можем написать просто // TO DOв том месте кода, где нужно указать, что требуется добавить функционал, но с другой стороны — оказывается есть варианты. Иногда требуется не просто указать, что чего не то не хватает, а в принципе наметить механику кода и ожидаемые функции и интерфейсы, проверив некоторую идею просто на наличие «дырок» в концепции. Так сказать, иногда нам нужен черновик. Именно черновые реализации (скорее, это больше похоже на строительные леса) и позволяет выводит микшин todo:

class WeatherStation
{
	int getTemperature()
	{
		mixin(todo!());
	}
	
	int diffTemperaturesByDay(int temp)
	{
		return temp - getTemperature;
	}
}

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

Добавить комментарий