Каким должен быть язык программирования? Анализ и критика Описание языка Компилятор
Отечественные разработки Cтатьи на компьютерные темы Компьютерный юмор Новости и прочее

Макросы — это чистые функции, исполняемые во время компиляции

Макросы в том виде, в котором они реализованы в Си и в его продолжателе C++, говорят о недоразвитости языка, поверх которого они работают. В самом деле, если язык так хорош, то зачем ему макросы? А потому они и нужны, что частенько чего-то не хватает и требуется расширить язык средствами другого языка. Макросы помогают спрятать недостатки основного языка. Язык макросов в Си и в C++ — это по сути это отдельный язык, имеющий мало общего с тем, поверх которого он выполнен.
Макросы

Во-первых, он не типизирован. Сочетание типизации в основном языке и отсутствие её в макросах противоречиво. Оно порою приводит к трудностям при компиляции, ошибки в макросах разгадываются порою труднее обычного.

Второй особенностью языка макросов является то обстоятельство, что макросы исполняются исключительно во время компиляции. То есть исполнение макросов — это транспиляция в основной язык.

А в-третьих результатом работы препроцессора является видоизменённый текст на основном языке. В то время как основной язык компилируется в исполняемый код.

Страуструп, автор C++:

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

Страуструпу макросы достались в наследство от Си. Но, несмотря на отрицательное к ним отношение, он вынужден был включить их в C++ для обеспечения совместимости.

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

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

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

x = 2 * 2		// операция "*" — чистая, вычисление 
			// результата возможно во время компиляции
y = sin(0)		// функция sin —чистая
ctg = cos(0)/sin(0)	// ошибка (деление на 0) выявляется при компиляцци
z = "число равно " + число в строку(x)
			// конкатенация строк во время компиляции
итого = сумма прописью (зарплата + премия)
			// если "сумма прописью" — чистая, а "зарплата" и "премия" — 
			// константы, то это вычисляется во время компиляции

Кстати, в Си есть псевдофункция sizeof(), которая чиста, имеет константный аргумент и работает во время компиляции. Предписание #include хотя и выполняет чтение файла, делает это во время компиляции. Следовательно, оно чисто.

Заключение

Любой язык программирования не нуждается в макросах, если в нём есть чистые функции — которые либо поддерживаются синтаксисом, либо свойство чистоты функции выводится на основании чистоты всех функций, в неё включенных, например:

int  F (int  a, int  b) {
	x = f1 (a);	// f1 — чистая
	y = f2 (b);	// f2 — чистая
	z = f3 (x, y);	// f4 — чистая
	return  z;	// в итоге F тоже чистая
}

Надо отметить, что развитие C++ идёт в направлении, в том числе, всё большего развития constexpr — вычисления выражений с константными аргументами. Но, надо сказать, что в C++ чистые функции не поддержаны синтаксисом. Вывод свойства чистоты у функций затруднён из-за правил этого языка, а в ряде случаев просто невозможен. Пока она лишь немного выходит за рамки арифметических и логических операций, свойство чистоты которых очевидно. Поэтому свёртка и распространение констант не могут быть глубокими. Поэтому выжать максимум в этом плане C++ не удастся. Это как раз тот случай, когда C++ вынуден поддерживать обратную совместимость, отрезая себе путь к совершенству. Но это уже другая тема.

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

Опубликовано: 2022.03.21, последняя правка: 2022.05.04    18:13

ОценитеОценки посетителей
   █████████████████████ 5 (50%)
   █████████ 2 (20%)
   █████ 1 (10%)
   █████████ 2 (20%)

Отзывы

✅  2022/08/23 13:34, Ильдар          #0 

ошибки в макросах разгадываются порою труднее обычного.

Да, есть такое дело. В рантайме можно посмотреть, проверить значения переменных, объектов. А во время компиляции поди ка, запусти отладчик.

✅  2022/08/30 18:10, void          #1 

Макросы настолько недоразвиты только в Си, в более других языках программирования мы видим более полноценные макросы.
  • HLA aka High Level Assembly by Randy Hide == такой занятный "ассемблер высокого уровня"

    Здесь мы видим синтаксис Паскаля, семантику скорее С++ (и даже вариант реализации STL, ООП, Win32 GUI) и довольно высокоуровневую стандартную библиотеку. STL и ООП построены на своём макроязыке. Он достаточно мощный чтобы обеспечивать рефлексивность, интроспекцию, CTFE = compile-time function execution.

    Этот "надассемблер" затем транслируется в обычный GAS/MASM/FASM или компилируется напрямую сразу в бинарник.
  • D с типизированными макросами разных видов: template mixin, template, string mixin
  • да даже PL/1! вы будете смеяться, но вот есть книжка про OS/2+PL/1. Там есть пример реализации PARSE конструкции из языка REXX на строковых макросах PL/1 как DSL. Макроязык PL/1 в реализации от IBM достаточно мощный, чтобы можно было вручную сконструировать текстовую строчку и подать её к компиляции.
  • Лиспы, тысячи их: начиная от простых интерпретируемых типа PicoLisp, NewLisp и прочих Lisp 1.5 до более удобных для компиляции.

    CrystalLisp https://github.com/vygr/ChrysaLisp от Chis Hinsley, автора Automania и прочих игрушек 80х, а также операционной среды/системы/виртуальной машины ElateOS, Tao OS (гуглите по ссылкам в профиле автора на Гитхабе, в Википедии, Вебархиве и osnews.com интервью). Как виртуальная машина — она реализовывала VPasm, виртуальный ассемблер (с бесконечным количеством регистров). В этом смысле немного напоминала LLVM bitcode, но если в LLVM представление SSA более удобно для компиляции и оптимизации, то здесь довольно простая и компактная кроссплатформенная реализация. Почитайте, там много интересного.
  • Sassy на Guile Scheme — ассемблер как макросы на Scheme.
  • Процедурные макросы Rust и например, реализация Форта на таких макросах.
  • DSL для Форта в виде встраиваемого ассемблера
...и прочие варианты либо простых макросов, либо гигиенических, либо "составимых CTFE функций высшего порядка".

✅  2022/09/03 23:35, Автор сайта          #2 

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

Вы закидали ссылками по теме макросов, как будто там есть что-то невиданное и необыкновенное. Да нет же, макросы — они и в Африке макросы. О них можно легко забыть, имея замену как минимум не хуже.

да даже PL/1! Вы будете смеяться

Возможно и Вы будете смеяться — когда-то писал такие макросы на PL/1, что аж на несколько страниц. Но решить задачу оказалось проще и быстрее без них. Так что программирование макросов в PL/1 оказалась для меня гимнастикой головного мозга.

✅  2022/09/06 19:46, void          #3 

Идея статьи не в том, как сделать макросы развитыми и удобными. А о том, чтобы сделать язык развитым и удобным.

Эти вещи связаны: чтобы сделать интеграцию composable языковых моделей, их можно сделать через более удобные макросы. Для чего, в свою очередь, язык должен быть более богат возможностями для реалиазации таких макросистем.

Достаточно развитый макроязык позволяет расширять базовый синтаксис, что в свою очередь, позволяет сделать, например, другой, более расширенный и удобный язык на этом метаязыке. Не нравится синтаксис Форта или Лиспа? Ну ок, прикрутите через макросы паскалевский или сишный. Или свой собственный.

Сам базовый Форт (или Лисп) тут становится не важен: он становится языком-носителем AST (или каких ещё, семантических) выражений. Макросистема тоже не столь важна по себе, сколько её возможности как средства реализации метаязыка. На котором реализуется транслятор целевого языка.

Макросы в том виде, в котором они реализованы в Си и в его продолжателе C++, говорят о недоразвитости языка, поверх которого они работают. В самом деле, если язык так хорош, то зачем ему макросы? А потому они и нужны, что частенько чего-то не хватает и требуется расширить язык средствами другого языка. Макросы помогают спрятать недостатки основного языка. Язык макросов в Си и в C++ — это по сути это отдельный язык, имеющий мало общего с тем, поверх которого он выполнен.

Ну если посмотреть примеры применений, для чего используются макросы. Посмотрим, например, в библиотеку Qt и порядок её сборки, а также любых примеров на Qt. Здесь мы видим MOC, "метаобъектный компилятор". Который, естественно, компилирует "метаобъекты".

То есть:
  • парсер MOC реализован отдельной утилитой.
  • из этого кодогенератор генерирует исходники на С++ c макросами, которые обрабатывают "сущности метаязыка" — SIGNAL, SLOT, подписку на события-обработчики.
Для чего тут почему-то понадобился отдельный язык каких-то особых макросов — ну, так получилось исторически. В GTK++ есть например libsignal или как-то так, где сигналы и слоты реализованы средствами обычного С++. В языке Vala (который прозрачно транслируется в C и объектную модель GObject) — это реализовано в рантайме.

Кстати, далее если сравнивать последние версии того же Qt. В Qt6 перешли на лямбды, constexprs и всё такое прочее. И переделали MOC на новом С++. Без отдельной утилиты и кодогенератора.

Опять же, если бы например MOC писали на D. Библиотека QtE Геннадия Мохова https://github.com/MGWL/QtE-Qt_for_Dlang_and_Forth реализовывала низкоуровневые привязки к C++ ABI. То есть, всё это метапрограммирование времени компиляции можно реализовать возможностями языка D. Возможностями старого С++ в Qt3, Qt4, Q4 — было невозможно. Возможностями нового С++ в Qt6 — более-менее полно возможно. Но всё равно, тот же возможности C++ для реализации удобного метапрограммирования не догоняют полноценные возможности метапрограммирования уровня того же D.

Язык является низкоуровневым — когда программы на этом языке переполнены подробной информацией о несущественном.

Алан Перлис

В этом смысле, Уолтер Брайт, реализовав Zortech C++ понял что Здесь уже ничего не исправить, жги, Господь! и решил реализовать "С++, сделанный правильно" — то есть, D.

Предписание #include хотя и выполняет чтение файла, делает это во время компиляции. Следовательно, оно чисто.

Non sequitur (по крайней мере, не всегда), да и не чисто оно вовсе.

Дело там нечистое технически, функционально нечистое:
https://stackoverflow.com/questions/11040133/what-does-defining-win32-lean-and-mean-exclude-exactly
#ifndef WIN32_LEAN_AND_MEAN
#include <cderr.h>
#include <dde.h>
#include <ddeml.h>
#include <dlgs.h>
#ifndef _MAC
#include <lzexpand.h>
#include <mmsystem.h>
#include <nb30.h>
#include <rpc.h>
#endif
#include <shellapi.h>
#ifndef _MAC
#include <winperf.h>
#include <winsock.h>
#endif
#ifndef NOCRYPT
#include <wincrypt.h>
#include <winefs.h>
#include <winscard.h>
#endif

#ifndef NOGDI
#ifndef _MAC
#include <winspool.h>
#ifdef INC_OLE1
#include <ole.h>
#else
#include <ole2.h>
#endif /* !INC_OLE1 */
#endif /* !MAC */
#include <commdlg.h>
#endif /* !NOGDI */
#endif /* WIN32_LEAN_AND_MEAN */
Пример применения:
#undef UNICODE

#define WIN32_LEAN_AND_MEAN

#include <windows.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdlib.h>
#include <stdio.h>

// Need to link with Ws2_32.lib
#pragma comment (lib, "Ws2_32.lib")
// #pragma comment (lib, "Mswsock.lib")

#define DEFAULT_BUFLEN 512
#define DEFAULT_PORT "27015"

int __cdecl main(void)
{
WSADATA wsaData;
int iResult;

SOCKET ListenSocket = INVALID_SOCKET;
SOCKET ClientSocket = INVALID_SOCKET;

struct addrinfo *result = NULL;
struct addrinfo hints;

int iSendResult;
char recvbuf[DEFAULT_BUFLEN];
int recvbuflen = DEFAULT_BUFLEN;

// <<Initialize Winsock>>
iResult = WSAStartup(MAKEWORD(2,2), &wsaData);
if (iResult != 0) {
printf("WSAStartup failed with error: %d\n", iResult);
return 1;
}

ZeroMemory(&hints, sizeof(hints));
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
hints.ai_flags = AI_PASSIVE;

// <<Resolve the server address and port>>
iResult = getaddrinfo(NULL, DEFAULT_PORT, &hints, &result);
if ( iResult != 0 ) {
printf("getaddrinfo failed with error: %d\n", iResult);
WSACleanup();
return 1;
}

// <<Create a SOCKET for the server to listen for client connections>>.
ListenSocket = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
if (ListenSocket == INVALID_SOCKET) {
///TL;DR ....
WSACleanup();
return 1;
}

// Setup the TCP listening socket
iResult = bind( ListenSocket, result->ai_addr, (int)result->ai_addrlen);
if (iResult == SOCKET_ERROR) {
///TL;DR
WSACleanup();
return 1;
}

freeaddrinfo(result);

iResult = listen(ListenSocket, SOMAXCONN);
if (iResult == SOCKET_ERROR) {
///TL;DR ...
WSACleanup();
return 1;
}

// Accept a client socket
ClientSocket = accept(ListenSocket, NULL, NULL);
if (ClientSocket == INVALID_SOCKET) {
///TL;DR ...
WSACleanup();
return 1;
}

// No longer need server socket
closesocket(ListenSocket);

// Receive until the peer shuts down the connection
do {

iResult = recv(ClientSocket, recvbuf, recvbuflen, 0);
if (iResult > 0) {
/// <<Handle request>>

// <<Echo the buffer back to the sender>>
iSendResult = send( ClientSocket, recvbuf, iResult, 0 );
if (iSendResult == SOCKET_ERROR) {
///TL;DR ...
WSACleanup();
return 1;
}
printf("Bytes sent: %d\n", iSendResult);
}
else if (iResult == 0)
printf("Connection closing...\n");
else {
///TL;DR ...
WSACleanup();
return 1;
}

} while (iResult > 0);

// <<shutdown the connection since we're done>>
iResult = shutdown(ClientSocket, SD_SEND);
if (iResult == SOCKET_ERROR) {
///TL;DR ...
WSACleanup();
return 1;
}

// <<cleanup>>
closesocket(ClientSocket);
WSACleanup();

return 0;
}
в этом "управлении конфигурацией" через макросы, если не определить Unicode — *U прототипы а не *A для ANSI — физически не слинкуются. Судя по тексту, NOCRYPT NOGDI также управляет конфигурацией, то есть во время компиляции этот #include возвращает разное — функционально нечистое, монада State в зависимости от того, определён ли нужный макрос конфигурационной единицы.

Ещё можно например попробовать #include <windows.h> включать ДВА раза, с #defined WIN32_LEAN_AND_MEAN или без. Вот такой он WINAPI — тощий и подлый. Концептуально функционально нечистый

✅  2022/09/08 15:20, Автор сайта          #4 

Достаточно развитый макроязык позволяет расширять базовый синтаксис, что в свою очередь, позволяет сделать, например, другой, более расширенный и удобный язык на этом метаязыке. Не нравится синтаксис Форта или Лиспа? Ну ок, прикрутите через макросы паскалевский или сишный. Или свой собственный.

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

А не говорит ли возможность улучшения синтаксиса языка о том, что действующий синтаксис не слишком хорош? Как говорит Юрий Антонов: «Пусть новые песни сочиняет тот, у кого старые плохие». Хочется макросов? Выбирайте Форт или Лисп — там точно есть возможность улучшения синтакcиса. Я же сторонник того, что язык должен быть настолько хорош, что нет надобности в его улучшении макросами.

Ну если посмотреть примеры применений, для чего используются макросы. Посмотрим, например, в библиотеку Qt и порядок её сборки, а также любых примеров на Qt.

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

Язык является низкоуровневым — когда программы на этом языке переполнены подробной информацией о несущественном.

Алан Перлис

Однако следует учитывать назначение и область применения языка. Например, в PHP типы параметров, автоматическое управление памятью, реализация массивов через ассоциативные массивы — это «несущественные детали реализации». Но попробуйте на этом языке написать ОС, и тогда эти детали становятся существенными! Мало того, что такая ОС будет еле ползать, так она будет «падучей»!

И с этим можно было бы побороться, да только язык не допустит — не даст инструментов управления «несущественными деталями»! Так что не стоит называть языки системного программирования языками низкого уровня, считать «низкий уровень» чем-то недостойным, чего стесняются приличные люди.

решил реализовать "С++, сделанный правильно" — то есть, D.

Язык D — шаг в правильном направлении. Но этот шаг получился не широким.

Non sequitur (по крайней мере, не всегда), да и не чисто оно вовсе. Дело там нечистое технически, функционально нечистое

Чтобы определить чистоту или её отсутствие, надо ответить на некоторые вопросы.

1) Есть ли там недетерминированность? Запрашивают ли макросы информацию извне, которая неизвестна во время компиляции?

Да нет же, все значения известны во время компиляции, они являются константами. Все без исключения макросы детерминированы.

2) Если программа неизменна и неизменны подключаемые библиотеки, то бывают у одинаковых компиляторов разные результаты компиляции?

Да нет же, компиляторы формируют одинаковый исполняемый код или выдают одинаковую ошибку.

Сдаётся, что этого достаточно, чтобы считать язык макросов чистым.

Теперь об остальном для void. Удалил большую часть Ваших комментарий — потому что не по теме. К тому же нет необходимости соревноваться с поисковой выдачей Гугла. Желающие в состоянии найти всё интересное для себя самостоятельно. Мнение Гугла можно узнать и без Вас. Лучше сосредоточиться на собственном мнении, если оно есть. Так же не надо соревноваться с Капитаном Очевидность, мы ещё не забыли, что Волга впадает в Каспийское море.

Написанное Вами обильно словами и предложениями. Но как всегда не систематизировано и не отформатировано. Поскольку Вы не берёте на себя труд приводить текст к читаемому виду, то и я этого не буду делать. Проще удалить.

✅  2022/09/08 15:03, Gudleifr          #5 

А не говорит ли возможность улучшения синтаксиса языка о том, что действующий синтаксис не слишком хорош?

При программирования решения конкретной задачи неизбежно возникает избыточность в силу того, что язык задачи не является языком программирования. И эта избыточность тем больше, чем универсальнее язык программирования. Макросы — способ уменьшения этой избыточности. Толковать их как носителей некоторой функциональной семантики не нужно. (Если вы не хотите в итоге получить TRAC).

✅  2022/09/08 21:59, void          #6 

2) Если программа неизменна и неизменны подключаемые библиотеки, то бывают у одинаковых компиляторов разные результаты компиляции?

Да нет же, компиляторы формируют одинаковый исполняемый код или выдают одинаковую ошибку.

Вот в этом примере с winsock например, первый #include <windows.h> подключит разные библиотеки, и сформирует разный исполняемый код в зависимости определены или нет WIN32_LEAN_AND_MEAN, NOGDI, NOCRYPT, и т.п. (а также, неявно UNICODE или ANSI).То есть, окружение — не чистое. Опять же, пример DEBUG и релизной сборки с _NDEBUG, который может в том числе быть передан ключём компиляции, а не в тексте. Опять же, -Lwinsock ключём компилятора или те же макросы, включающие или отключающие pragma lib.

Опять же, например gcc. Который запускается из makefile через дефолтные переменные окружения, например, CC=x86_64-w64-mingw32-gcc.exe (кросскомпилятор) или CC=gcc (родной). Потому что сам gcc — лишь враппер, скрипт для запуска нужного, с указанием триплета.

У одинаковых компиляторов разные результаты тоже бывают — соберите например clang-ом, msvc или gcc C++-ный код. Из-за отсутствия стандартизации C++ABI и разного манглинга код тоже будет разный.

По первому пункту тоже можно придумать макрос, когда результат конкатенации A##B будет разный
(примерный аналог из bash-измов if [ x$y=="x" ] ; then ... fi) Ну и классический ошибочный пример, со скрытыми граблями: #define max(a,b) (a<b?a:b) Если скобками не обнести фактические параметры, приоритет и результат будет разный. Потому что подставлются буквально строки как текст, а не как структурные выражения.

template, кстати понимает структурные выражения, что позволяет через трюки вроде SFINAE сделать тело и хвост рекурсии. Например, у К. Книжника так в GOODS сделана сериализация объектов и метаобъектный протокол (да-да, на С++98). Тоже будете говорить, что рекурсия детерменирована? Но да, это уже не совсем просто макрос. То есть, язык макросов всё ещё слишком богат, чтобы обеспечивать их результат как чистые функции (по крайней мере, не во всех случаях). Опять же, для практически примеров применений полезных макросов с Тьюринг-полным языком это не мешает, а наоборот, помогает, например, написать парсер времени компиляции. Примеры — см. в удалённых.

Помимо конкатенации недетерменированные макросы можно вставить через __LINE__, __FILE__, #line, __DATE__, __TIME__, __TIMESTAMP__, __COUNTER__

А вот например, бейсик на M4: http://www.basic-converter.org/m4basic/, на макросах MASM: https://retrobasic.allbasic.info/index.php?topic=358.0 (транслируется макросами в ассемблер). Видно, что достаточно развитый макроязык позволяет довольно просто реализовать парсер времени компиляции.

✅  2022/09/08 22:50, void          #7 

h.c=
#include <stdio.h>
#define asd45 45
#define asd43 43

#define cat( x, y ) x ## y
#define xcat( x, y ) cat( x, y )

int main () {
printf ("%d\n", xcat(xcat( asd, 4), D) );
return 0;
}
gcc h.c -DD=3 -o h3.exe
gcc h.c -DD=5 -o h5.exe
запуск h3 выводит 43, а запуск h5 — 45

вот вам недетерменированное окружение макросов.

✅  2022/09/08 23:51, Автор сайта          #8 

Gudleifr

А кому нужны «языки задачи»? В Excel есть свой «язык задачи». Пользователи почти поголовно шарахаются от него: «Ой, это сложно. Это надо программистам показать». Иллюзии прошлых лет о всеобщей компьютерной грамотности посредством обучения программированию давно прошли. Предсказание, что «каждый бухгалтер будет знать программирование» не сбылось. Оно вернулось бумерангом: теперь каждый программист обязан знать бухгалтерию.

Сколько знал бухгалтеров — ну никто не удосужился выучить SQL — казалось бы, вполне бухгалтерский язык, «язык задачи». Так что как занимались программированием только программисты, так и будут заниматься. Разве что отдельные продвинутые инженеры в силу любопытства (а не профессиональных обязанностей) и в качестве исключения могут заняться «языком задачи».

void

#include <windows.h> подключит разные библиотеки, и сформирует разный исполняемый код в зависимости определены или нет WIN32_LEAN_AND_MEAN, NOGDI, NOCRYPT, и т. п.

Если WIN32_LEAN_AND_MEAN и другие определены, то для одинаковых наборов макроконстант будут выполнены одинаковые сценарии подключения или неподключения библиотек.

дефолтные переменные окружения

Да, это я упустил, забыл упомянуть. У них одинаковая роль с макроконстантами: они выполняют один и тот же сценарий для одинаковых значений.

У одинаковых компиляторов разные результаты тоже бывают — соберите например clang-ом, msvc или gcc C++-ный код.

Пишите про одинаковый компиляторы, а сами указываете разные. Одинаковые — значит совпадающие до единого бита!

Тоже будете говорить, что рекурсия детерминирована?

А разве рекурсия противопоказана детерминированности? Чистота означает, что одинаковым параметрам на входе всегда соответствуют одинаковые результаты на выходе. Рекурсия и чистота перпендикулярны друг другу.

Но убеждать Вас больше ни в чём не буду. Вы вправе иметь собственное мнение.

✅  2022/09/08 22:55, void          #9 

не является * чистой функцией высшего порядка, конечно же.

✅  2022/09/08 23:01, Gudleifr          #10 

А кому нужны «языки задачи»?

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

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

✅  2022/09/08 23:02, void          #11 

В общем, вот в этом примере с xcat почему нужна именно обёртка в виде xcat а не сама cat как чистая функция высшего порядка — пример наглядно показывает ограничения препроцессора при раскрытии строк и лексем.

Именно в том, что это должны быть строки или лексемы — а не структурированные выражения. Сюда же недетерминированность раскрытия неправильного
#define min(a,b)  a<b?a:b
и необходимость феерической расстановки скобок (т.е., UNQUOTE и SPLICE этого искусственно созданного структурного выражения — ибо буквальный QUOTE этим макросом выдаёт выражения не того типа).

✅  2022/09/09 14:24, Автор сайта          #12 

посмотрите на любое оконное приложение — вы увидите, что набор "кнопок", "боксов" и "менюшек" образует вполне себе "язык"

Графический интерфейс — это как-то сбоку. Нельзя сказать, что между ним и макросами связь более тесная, чем между ним и языком программирования без макросов. Нельзя сказать, что макросы в результате развития стали графическим интерфейсом, то есть «языком задачи».

Сюда же недетерминированность раскрытия неправильного #define min(a,b) a<b?a:b

Тут, скорее всего, имеет место быть в ошибка, заложенная в Си отцами-основателями. У меня как-то был разговор на тему «как заставить препроцессор ошибиться» в одной отечественной компании, разработчике компилятора C++. Ошибка, думаю, не устраняется намеренно для поддержания обратной совместимости. Но это никак не теоретически непреодолимый изъян. Не стоит частный случай распространять на общую картину.

Допустим, у нас есть рекурсивная функция. Если она является чистой, это заставляет её закцикливаться или работать как-то неправильно? Нет, она от этого не «ломается». А если ей на вход подать константные аргументы, то это как-то испортит её? Да нет же.

✅  2022/09/09 14:42, Gudleifr          #13 

Графический интерфейс — это как-то сбоку. Нельзя сказать, что между ним и макросами связь более тесная, чем между ним и языком программирования без макросов. Нельзя сказать, что макросы в результате развития стали графическим интерфейсом, то есть «языком задачи».

Речь не о том, что "что-то чем-то стало". Речь о том, что реализация на языке программирования языка задачи ведет к росту избыточности. Например, попытка написать на MASM решение OPENGL-задачи (фрагмент):
invoke	SetLightSource,GL_LIGHT0,ADDR LightSourcePosition,ADDR LightAmbient
invoke SetLightSource,GL_LIGHT1,ADDR LightSource2Position,ADDR Light2Ambient
invoke SetLightSource,GL_LIGHT2,ADDR LightSource3Position,ADDR Light3Ambient
invoke CreateSphere,1,GLU_FILL,GLU_SMOOTH,ADDR Sphere1Color,ADDR Sphere1Radius,sphere1Parts
mov GlSphere1,eax
invoke CreateSphere,2,GLU_FILL,GLU_SMOOTH,ADDR Sphere2Color,ADDR Sphere2Radius,sphere2Parts
mov GlSphere2,eax
invoke CreateSphere,3,GLU_FILL,GLU_SMOOTH,ADDR Sphere3Color,ADDR Sphere3Radius,sphere3Parts
mov GlSphere3,eax
Видите, сколько здесь повторяющихся подстрок?

✅  2022/09/13 23:50, Бурановский дедушка          #14 

Долой практику метаязыков!

Существуют замечательные языки, придуманные не для выполнения задач, а для создания языков, которые способны их выполнять. Racket, Rebol и Forth — лишь несколько примеров. Они все мне нравятся, играть с ними — чистое удовольствие. Но, как вы наверное догадались, удовольствие, получаемое от работы с языком, — не главный критерий, делающий язык универсальным и популярным.

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

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

Отсюда: «Если изобрести язык программирования 21 века», https://habr.com/ru/company/wirex/blog/431558/

✅  2022/09/14 00:09, Gudleifr          #15 

Возможность создавать новые языки внутри языка для выполнения той или иной задачи — очень мощное средство

Это плюс.

И вот тут начинаются проблемы

Это тоже плюс.

✅  2022/10/19 18:31, Виктор          #16 

Как вариант, можно явно указать при использовании функции, что бы она посчиталась на этапе компиляции, и если надо, отладить её во встроенном интерпретаторе.

Например, в синтаксисе Си это могло бы выглядеть так:
StrLen = @strlen(ConstStr);
Мне изначально не нравилось, когда пишешь функцию и сразу ей даешь способ использования inline, хотя как её использовать, inline или обычный вызов, решаешь лишь на этапе использования функций.

✅  2022/10/20 15:06, Автор сайта          #17 

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

Если хороши и язык, и компилятор, то «inline» должно отсутствовать в языке. Делать подстановку вместо вызова функции или нет — должен решить компилятор в зависимости от опций, которые ему переданы.

✅  2022/10/31 21:16, void          #18 

Приведу цитату полностью:

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

Ребята, пишущие на Lisp, прошли через это в 80-х. Они поняли, что чем больше прикладных элементов языка будут стандартизированы, тем лучше. Так на свет появился Common Lisp. И он оказался огромен. Стандарт INCITS 226–1994 насчитывает 1153 страницы. Этот рекорд 17 лет спустя побил только C++ со стандартом ISO/IEC 14882:2011 (1338 страниц). С++ приходится тащить за собой неподъемный багаж наследия, хотя он не всегда был таким большим. Common Lisp был создан по большей части с нуля.

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

Да, мне тоже не нравится Common Lisp — как стандарт, как исторический артефакт. Хотя он может быть довольно практичен как язык и среда.

Для полноты картины, возьмём например sectorlisp: http://justine.lol/sectorlisp/ http://justine.lol/sectorlisp2/ — метациклическая реализация Лиспа со сборщиком мусора размером 436 байт, влезающая в бутсектор. Кстати, автор того сайта, девушка, попутно изобрела технологию по-настоящему кроссплатформных бинарников: http://justine.lol/ape.html и написала отладчик/эмулятор http://justine.lol/blinkenlights/ Этот Лисп можно загрузить в виртуальный компьютер, посмотреть в отладчике, в мониторе. Реализация довольно подробно описана. Это почти как "Уравнения Максвелла для теории поля", в программировании — элегантно, компактно и наглядно, даже красиво:
https://michaelnielsen.org/ddi/lisp-as-the-maxwells-equations-of-software/
https://www.gnu.org/software/mes/manual/html_node/LISP-as-Maxwell_0027s-Equations-of-Software.html
(кстати, mes сам по себе https://www.gnu.org/software/mes/manual/html_node/index.html#SEC_Contents тоже довольно наглядная реализация связки интерпретатор Scheme/компилятор Си, используемая для минималистичной самораскрутки дистрибутива Guix и его minimal seed среды сборки; в общем-то, далее на Scheme можно было бы написать например ассемблер, например, как здесь: https://sassy.sourceforge.net/и раскрутить бутсектор на таком ассемблере, как здесь https://linuxgazette.net/issue77/krishnakumar.html.

Простой интерпретатор вроде picolisp тоже похож на эту реализацию "Lisp в бусекторе". Эти — Лиспы диалекта Lisp 1.5, то есть, ранние динамические Лиспы где 1.5 области видимости, пространств имён — для функций и для переменных. Далее это эволюционировало в MDL, BBN Lisp и InterLisp, MacLisp — например, InterLisp.org сам по себе представляет собой динамичную реализацию с ООП в духе Смоллтока, FLAVOURS. Потом это унифицировалось и стандартизировалось в пиджин, который назвали CommonLisp, стандартизированный ANSI. Получился объёмистый стандарт и описание реализации "де факто" CLtL2 объёмом ~1200 страниц. В 1990х появился другой стандарт, например ISO ISLISP. Объёмом ~120 страниц. Примерно в те же времена стандартизировались диалекты Схемы, RNRS: https://github.com/schemedoc/rnrs-metadata (здесь https://small.r7rs.org/ тоже есть ссылка на стандарты Схемы).

Сравнивая стандарты по объёму: R4RS.pdf — 55 страниц, R5RS.pdf — 50 страниц, R7RS — 88 страниц. Для комплектности, дополним стандартом Kernel схемы by John Shutt, изобрётшим vau-expressions (Kernel — это Cхема с first class environments: https://web.cs.wpi.edu/~jshutt/kernel.html), оттуда ссылка на The Revised -1 Report on the Kernel Programming Language (revised 29 October 2009) is available in pdf and gzipped postscript — 191 cтраниц, из которых описание собственно языка — 57 страниц, остальное — описание стандартной библиотеки.

Что можно понять, сравнивая эти стандарты лиспов, и реализации стандартов? Во-первых, CommonLisp язык избыточно сложный — как по историческим причинам ("де факто" стандартизация общих фич трёх диалектов), так и из-за некоторой концептуальной нецелостности, отсуствия достаточно унифицирующей общей идеи. Например: FEXPS as Ultimate abstraction. В ANSI CommonLisp например, перечислен десяток специальных форм. Которые и не функции, и не макросы, а некоторые разновидности compiler-macro, для эффективной компиляции. В ISO ISLISP этих специальных форм уже меньше, и унифицирующая концепция тоже присутствует — все типы являются объектами, классами в объектной системе ILOS. В языках Lisp-1, то есть, Схемах — такой концепцией выступают гигиенические модульные макросы (не засоряющие основное пространство имён), и отображение syntax objects на datum; а также — замыкания, продолжения (для реализации исключений и объектной системы)

В языке Kernel такой сильной идеей, унифицирующей концепцией выступает environments as first-class objects.

Например, язык ALGOL делали лингвисты. Поэтому читая описание языка, можно наткнуться на "внешние и внутренние объекты", то есть: знак и денотат некоторого концепта. При этом они уже понимают, что имя — это адрес объекта, но не указатель, а типобезопасная ссылка. И начинают рассуждать о call-by-name семантике. Но всё ещё возможно создать внутренний объект, которому не соответствует однозначно какой-либо внешний. Например, монада do из Хаскеля и семантика последовательного выполнения, побочных эффектов в императивном программировании. Она выделяет побочные эффекты явно. Есть некоторый объект, монада do которая локализует побочные эффекты. И остальные монады, которые потенциально могут выполняться БЕЗ таких вот побочных эффектов. Коэффекты и контексты например позволяют задать такую область локализации побочных эффектов — другим способом.

Что же до способа Алгола классического — у внутреннего объекта, денотата "окружение", "пространство имён" вообще нет соответствующего знака: внешнего синтаксического объекта. Поэтому в процедурном языке программирования алгоритмов, Алголе, нет и способа рассуждать об этом: типобезопасность этого пространства имён, окружения environments, хранящего привязки bindings имён переменных, символов, знаков к соответствующим денотатам значений конкретных типов. Нет алгольного типа, соответствующего "пространству имён". Поэтому и утверждение типа "эта процедура не содержит побочных эффектов" — принципиально неверифицируемо.

Затем ровно та же концептуальная нецелостность перекочевала из Алгола в Паскаль и Си. В итоге мы вполне официально имеем Undefined Behaviour, дыру в стандарте. Которая показывает лишь то, что концепции и семантика абстрактного исполнителя с моделью памяти и т.п. в этих языках не полностью определена. Что ещё можно понять, сравнивая эти стандарты лиспов, и реализации стандартов?

Во-вторых, например из этих ~1200 cтраниц стандарта CommonLisp довольно большую часть занимает описание стандартной библиотеки и стандартной объектно-ориентированной системы CLOS, и метаобъектного протокола с рефлексией. Которая в большинстве своём опять же описана как ad hoc реализация, стандартизировали эволюционно то, что было: CLOS на аналоге фреймовой модели представления знанией, то есть, агентов, то есть, объектов; примерно как FLAVOURS на основе прототипно-метаклассного ООП в духе Смоллтока и SELF.

Опять же, реализация например ILOS в ISO ISLISP более компактна; объектно-ориентированные системы в Scheme тоже, на замыканиях, продолжениях и модульных синтаксических макросах с гигиеной. Что характерно — ООП-система не встроена в язык, а является всего лишь ещё одной альтернативной библиотекой макросов. То есть, возможно реализовать несколько ООП систем, разными библиотеками.

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

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

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

То есть, теория машин и механизмов — для конструирования подходящих по случаю и задаче велосипедов с нужными характеристиками (если готовые стандартные с квадратными колёсами и почему-то не едут)

✅  2022/10/31 21:48, void          #19 

Графический интерфейс — это как-то сбоку. Нельзя сказать, что между ним и макросами связь более тесная, чем между ним и языком программирования без макросов. Нельзя сказать, что макросы в результате развития стали графическим интерфейсом, то есть «языком задачи

Но могли бы быть. 1) Например, возьмём оконную систему Sun NeWS на основе Display PostScript. Здесь векторный GUI отрисовываемый через Display PostScript реализован как объекты оконного сервера, этого самого NeWS. PostScript — функциональный Форт со сборкой мусора. Display PostScript добавляет к этому объектно-ориентированную систему, на основе словарей PostScript. Которая может расширяться откомпилированными Си функциями, коллбеками.

2) Читая воспоминания автора Pandoc. Pandoc понимает в качестве исходника Literate Haskell. То есть, сам Pandoc может транслировать такие вот *.lhs литературные исходники — в другие форматы документации. То есть: такой *.lhs исходный текст это одновременно и документация, и программа. То есть — гипертекст. В итоге — вот пример, из чего можно было бы сконструировать такую систему. Например, в текстовом виде вроде markdown для Pandoc, GUI blueprints для PlantUML, задаются ресурсы GUI диалогов. Которые затем транслируются в исполняемый код (объекты Display PostScript на сервере + Си коллбеки на клиенте) и документацию в духе doxygen или Noweb или FunnelWeb, со всеми нужными гиперссылками. Под макросами здесь понимается скорее всего реализация такого DSL для GUI ресурсов. Который может быть реализован в том числе и не макросами, да. Например, из упражнений из презентации того автора Pandoc:
https://johnmacfarlane.net/BayHac2014/#/ https://github.com/jgm/BayHac2014/blob/master/slides.txt

Здесь в репозитории приводятся примеры обработки объектов Pandoc такой вот алгеброй программ API и GADT на Хаскеле, вызывая pandoc как библиотеку. Которые (если бы макросы действительно были бы чистыми функциями, как функциональные объекты, в смысле first class objects as enviroments, objects, documents, gui resources, et cetera et cetera et cetera) — могли бы быть чем угодно, по сути.

В похожем духе ещё реализован GUI в графической библиотеке Nile/Gezira,Maru, COLA из проекта FoNC/STEP:
https://news.ycombinator.com/item?id=19844088 https://news.ycombinator.com/item?id=26604813
KScript, UI building on top of Nile: https://www.freudenbergs.de/bert/publications/Ohshima-2013-KScript.pdf
http://tinlizzie.org/dbjr/KSWorld.ks/

Dот это: http://tinlizzie.org/dbjr/KSWorld.ks/index.pp, например, вполне могло бы стать таким вот GUI.

Добавить свой отзыв

Написать автору можно на электронную почту
mail(аt)compiler.su

Авторизация

Регистрация

Выслать пароль

Карта сайта


Содержание

Каким должен быть язык программирования?

Анализ и критика

●  Устарел ли текст как форма представления программы

●  Русский язык и программирование

●  Многоязыковое программирование

Синтаксис языков программирования

Синтаксический сахар

●  Некоторые «вкусности» Алгол-68

●  «Двухмерный» синтаксис Python

●  Почему языки с синтаксисом Си популярнее языков с синтаксисом Паскаля?

●  Должна ли программа быть удобочитаемой?

●  Стиль языка программирования

●  Тексто-графическое представление программы

●●  Разделители

●●  Строки программы

●●  Слева направо или справа налево?

●  Комментарии

●●  Длинные комментарии

●●  Короткие комментарии

●●  Комментарии автоматической генерации документации

●●  Нерабочий код

●●  Помеченные комментарии

●  Нужны ли беззнаковые целые?

●  Шестнадцатиричные и двоичные константы

●  Условные операторы

●  Переключатель

●  Циклы

●●  Продолжение цикла и выход из него

●  Некошерный «goto»

●  Изменение приоритетов операций

●  Операции присвоения и проверки на равенство. Возможно ли одинаковое обозначение?

●  Так ли нужны операции «&&», «||» и «^^»?

●  Постфиксные инкремент и декремент

●  Почему в PHP для конкатенации строк используется «.»?

●  Указатели и ссылки в C++

●●  О неправомерном доступе к памяти через указатели

●  Обработка ошибок

●  Функциональное программирование

●●  Нечистые действия в чистых функциях

●●  О чистоте и нечистоте функций и языков

●●  Макросы — это чистые функции, исполняемые во время компиляции

●●  Хаскелл, детище британских учёных

●●  Измеряем замедление при вызове функций высших порядков

●●  C vs Haskell: сравнение скорости на простом примере

●●  Уникальность имён функций: за и против

●●  Каррирование: для чего и как

●●  О тестах, доказывающих отсутствие ошибок

●  Надёжные программы из ненадёжных компонентов

●●  О многократном резервировании функций

●  Использование памяти

●  Почему динамическое распределение памяти — это плохо

●  Как обеспечить возврат функциями объектов переменной длины?

●●  Типы переменного размера (dynamically sized types, DST) в языке Rust

●●  Массивы переменной длины в C/C++

●●  Размещение объектов в стеке, традиционный подход

●●  Размещение объектов переменной длины с использованием множества стеков

●●  Размещение объектов переменной длины с использованием двух стеков

●●  Реализация двухстековой модели размещения данных

●●  Двухстековая модель: тесты на скорость

●●  Изменение длины объекта в стеке во время исполнения

●●  Размещение объектов переменной длины с использованием одного стека

●  Можно ли забыть о «куче», если объекты переменной длины хранить в стеке

●  Безопасность и размещение объектов переменной длины в стеке

●  Массивы, структуры, типы, классы переменной длины

●  О хранении данных в стеке, вместо заключения

●  Реализация параметрического полиморфизма

Описание языка

Компилятор

Отечественные разработки

Cтатьи на компьютерные темы

Компьютерный юмор

Новости и прочее




Последние отзывы

2024/10/15 22:49 ••• Неслучайный читатель
Русский язык и программирование

2024/10/14 18:05 ••• MihalNik
Энтузиасты-разработчики компиляторов и их проекты

2024/10/01 09:36 ••• Иван
О русском ассемблере

2024/09/30 00:08 ••• Автор сайта
Новости и прочее

2024/09/29 23:40 ••• Автор сайта
Десятка худших фич C#

2024/09/29 13:10 ••• Автор сайта
ЕС ЭВМ — это измена, трусость и обман?

2024/09/22 21:08 ••• Вежливый Лис
Бесплатный софт в мышеловке

2024/09/05 17:44 ••• Автор сайта
Правила языка: алфавит

2024/09/04 00:00 ••• alextretyak
Циклы

2024/09/02 22:24 ••• Автор сайта
Постфиксные инкремент и декремент

2024/08/26 00:37 ••• Автор сайта
Что нового с 1966 года?

2024/07/26 13:32 ••• Бурановский дедушка
Программирование исчезнет. Будет дрессировка нейронных сетей

2024/06/21 00:20 ••• Gudleifr
О превращении кибернетики в шаманство