мне до сих пор была интересна эта серия статей, так что я бы подождал одну про синхронизации, спасибо.
кроме раста я наверное не знаю языков, в которых к синхронизации как-то принуждают (но я не особо и разбирался в теме), так что также будут интересны и сравнения (в расте же вроде есть трейты Sync и Send, которые мешают передавать значения нарушающие синхронизацию в другой поток — даже если разымплементировать их надо вручную для чего-то самописного; и чтобы взять ссылку из значения под мьютексом, нужно его залочить, если память не изменяет)
в той же java хочешь пиши synchronized-блоки, хочешь нет, вроде как, а плюсы как всегда позволяют тебе делать что хочешь, но по итогу слишком уж на отвали. (но опять же, я не очень прямо хорошо погружён, я лишь студент, который многопоток использовал лишь пару раз, так что могу ошибаться)
----
по поводу конструкторов:
в расте у полей значения по умолчанию запрещены, и каждое поле нужно обязательно инициализировать; чтобы же получить значение по умолчанию нужно использовать Default::default() или какой-нибудь _::new(), а чтобы проинициализировать напрямую нужноуказать значение для кпждого поля по отдельности.
противоположным является подход например в C, Java или C#, где если не указать значение по умолчанию, то выберется конструктор по умолчанию, null или т.п. Конечно, в C# есть required, который правда всё равно для части случаев будет так себе, а в плюсах можно написать какой-то собственный простенький тип, в котором будет что-то вроде
, но как будто это костыли, о которых разработчики вряд ли будут думать (а может и будут, я не очень знаю).
подход аргентума я вижу похожим, но хоть немного лучшим в том, что значение по умолчанию нужно выбрать самим.
заодно синтаксис value.{/*lambda*/} мне напоминает что-то похожее в сишарпе или джаве (хотя наверное в котлине оно и вовсе будет околоидиоматичным, хотя и не уверен):
впринципе, в C# (и Rust тоже вроде как) также можно и сделать extension-метод, чтобы работал так же как also в котлине, но всё равно котлин в этом плане как будто красивейший, и подход из аргентума мне напоминает его, тем более что это ещё и идёт по умолчанию — по дизайну решение безусловно красивое.
ну а так, мне тоже нравится будто бы повсеместный отказ от конструкторов (и от перегрузок функций в частности)
просто в аргентуме значение по умолчанию будто бы словно есть абсолютно всегда, если я правильно понимаю, что не во всех ситуациях полезно (условно не существует же никакого сокета или дескриптора файла по умолчанию — верно? а если и есть, то стоит считать, что он должен вести себя равно какому-нибудь null, не так ли?)
по поводу классов: от того, что всегда есть значения по умолчанию, всё хорошо будет, не в этой модели, а в общем случае? и не будет ошибок с тем, что потенциально можно пропустить инициализацию какого-то поля с данной моделью их инициализации? хотя понимаю, что более старые языки тоже многие к этому склонны, но всё-таки.
и если тут утверждается, что тут счёт ссылок неатомарный, то что будет в многопотоке?
есть ли какая-то гарантия что я не протащу слабую ссылку в другой поток имея возможность редактировать основную из этого?
последний абзац я уже не понимаю, ибо так всей этой теорией не интересовался, но я просто думаю, что для N-мерного пространства можно определить N-мерный тензор, который как бы будет результатом перемножения 0 элементов, и затем уже получать то что нужно как было сказано через тензорное произведение на векторы-множители
чисто технически, есть ведь ещё и типы-степени (функции)… сосредотачиваться лишь на суммах и произведениях — не так хорошо, я полагаю, но как бы с другой стороны, функции должны быть даже в тех япах, где нету структур… немного спускаясь в полуфилософскую шутливость, операции открылись словно наоборот от классической алгебры. если конечно не вспоминать о first class – функциях и замыканиях…
В этом есть какие-то минусы кроме нарушения около-стереотипа о том, что раз умножение, то обязательно именно двух элементов?
К тому же такое умножение 4 векторов всё равно работало бы также как и для 3д-умножения — результатом будет пятый, вектор перпендикулярный этим четырём, при том что его длина будет соответвовать площади гиперпараллелепипеда между ними. Как минимум, поиск перпендикулярного вектора это задача очень часто реально полезная.
да и если и рассматривать только пространства где произведения могут быть только от двух элементов, с этим же вопросом было бы интересно подойти по поводу того, как понять для ккких пространств определено произведение от n элементов для произвольного неотрицательного целого n. если условно произведение от 1 элемента определено для каждого пространства, от 2 лишь для 0, 1, 3, 7, от 0 при беглом обдумывании непонятно как определять кроме как для 1- и 0-мерного пространства — и вот это типа обобщить для произвольного фиксированного числа множителей.
так-то да, просто я посчитал управляющие символы так же за символы.
но если делать именно так, то для условного о̴̷̰͙͓̰ͫ͗̉͜ͅ никаких 32 бит не хватит.
при том, для часто используемых сочетаний символ-лигатура (ё, だ, ã, …) и так есть отдельные кодпоинты. отдельно взятые лигатуры ведь нужны именно когда нужно олигатурить произвольный символ, нет? (хотя отдельно взятого э́ какого-нибудь не хватает или отдельных кодпоинтов для разных вариаций одного символа (привет, cjk) — но тут скорее вопрос к создателям юникода)
[[maybe_unused]] добавили в C++17. Даже до этого, если нет согласия с линтером и есть достаточная уверенность, что он брешет, вариант с (void) или любой другой подобной глушилкой был бы не так плох.
Собственно, я впринципе с точки зрения линтера не особо понимаю, в чём проблема, если переменная не использовалась в одной из веток — важнее же чтобы использовалась хотя бы в одной, разве нет?
Я не особо математик и могу чего-то не знать, поэтому скажу лишь свои мысли как обывателя.
Как будто векторное произведение определёно на любой мерности, просто придётся работать с тензорами, а не векторами.
Но вообще, тогда лучше впринципе определить не тензор как результат век. произведения двух векторов, а тензор как век. произведение ноля вектороа — а потом домножать вектора на этот тензор. — То есть, определить некий тензор исходя из самого пространства.
Но вообще, если не лезть в тензоры, можно просто сказать, что век. произведение в ланной мерности N определено для N - 1 векторов.
Способ считать век. произведение как детерминант матрицы, где самая верхняя строчка это единичные векторы очень легко обобщается до многомерных пространств. Я этим в школе для своих развлечений вроде как типа и пользовался.
это не так? в 4-мерном пространстве двум векторам может быть перпендикулярна плоскость — не значит ли это, что любой вектор между двумя точками этой плоскости будет перпендикулярен изначальному вектору?
jit-компилятор мб смог бы, а вот aot только если в несколько проходов. ну или сделать какую-то статическую переменную на случай "ладно, не хочу". но не думаю, что aot-компилятороделы будут использовать такой динамический подход.
Может быть, всё же дело в том, что я не джавист и джавовские практики мне не знакомы. Если так рассудить, явные исключения действительно схожи с Result, а неявные с panic. Но, в какой-то мере, мне кажется, что проблема исключений в некоторой степени и в том, что ты с ними работать будешь как с исключениями. Возможно, было бы удобно всё же сделать какой-то Result типа такого, чисто чтобы что-то делать локально внутри функции и локально с результатом конкретной функции — хотя это, наверное, и грозит потерей стектрейса (или нет, я не помню, как это работает).
public sealed interface Result<T, E extends Exception> {
record Ok<T, E extends Exception>(T value) implements Result<T, E> {
public T unwrap(){
return value;
}
public T rethrow() {
return value;
}
public <T0> Ok<T0, E> map(Function<T, T0> fn) {
return new Ok<>(fn.apply(value));
}
//and other
}
record Err<T, E extends Exception>(@NotNull E err) implements Result<T, E> {
public T unwrap(){
throw new RuntimeException(err);
}
public T rethrow() throws E {
throw err;
}
@SuppressWarnings("unchecked")
public <T0> Err<T0, E> map(Function<T, T0> fn) {
return (Err<T0, E>)this;
}
//and other
}
@FunctionalInterface
interface ThrowingFn<T, E extends Exception>{
T run() throws E;
}
@SuppressWarnings("unchecked")
@NotNull
static<T, E extends Exception> Result<T, E> catch_result(ThrowingFn<T, E> fn) {
try {
return new Ok<>(fn.run());
} catch (Exception e) {
return new Err<>((E)e);
}
}
static<T> T unwrap_result(ThrowingFn<T, ? extends Exception> fn) {
try {
return fn.run();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
T unwrap();
T rethrow() throws E;
<T0> Result<T0, E> map(Function<T, T0> fn);
//and other
}
Во всяком случае, я думаю, что есть пару вещей, которые изменить бы хотелось, чтобы всё работало приятнее:
Синтаксис
Условный try{ } catch(ExceptionType e) {} лично для меня имеет пару проблем в дизайне:
1) Фигурные скобки вокруг try и catch обязательные — рассчитаны лишь на то, чтобы оборачивать всю функцию — при необязательности для остальных конструкций.
2) Тип исключения — если throws функции лишь одного типа, тип в catch можно бы и вывести из контекста
3) Блок try всегда возвращает void — нужно заводить переменную вне блока для результата.
4) Сахар на try{ } catch(ExceptionType e) {throw new RuntimeException(e)} всё же был бы приятен
Неявность и схожесть с неявными исключениями — поставь throws Exception на функцию — и не заметь throw там, где ты уже хочешь его поймать.
Не помешали бы существующие отдельно методы, кидающие явные и неявные исключение в тех случаях, когда принято кидать лишь неявные.
Неудобность с точки зрения типизации. Опять же, условный Function<A, B> уже запрещает кидать явные эксепшены. Если бы явные исключения становились частью возвращаемого типа, было бы удобнее. Хотя и можно сделать условный ThrowingFunction<ArgT, ResT, ExT extends Exception>, навроде того, что я написал сверху, но так-то большинству библиотек и фреймворков всё же будет всё равно.
Но может быть и такое, что это всё вообще не нужно, всё и так работает замечательно, я не прав и у меня мозг просто немного оrustенел, так сказать.
Товарищ, вы боритесь менее, чем за 1 строку кода! Остановитесь! Разработчику полезно знать, где вообще подобный эксепшон в перспективе может появиться.
Я сам не джавист, но всё же условно в вузе с джавой мы работали и за это время я заметил маленький минус у такого подхода. Условно я не могу написать так:
Function<A, B> f = this::someMethodThatThrows;
По этому мне придётся оборачивать:
Function<A, B> f = a -> {
try {
someMethodThatThrows(a);
} catch (Exception e) {
throw RuntimeException(e);
}
};
Я всё же считаю, что такое оборачивание является некоторой проблемой и не так далеко ушло от какого-то err != nil в go — хоть и не нужно так часто.
Предположу, что отличие в том, что речь идёт о том, что инкапсуляция будет происходить на уровне файлов, а не на уровне линковки.
То есть условно если в классике, конечный пользователь скачает сразу файл (jar/dll/…), где будет уже динамически выбираться реализация, то здесь, я так понял, речь о том, чтобы на уровне конечного пользователя скачивать файл с реализацией в зависимости от цели.
Интересно, именно поэтому был придуман COM? — условно у нас в файлах где-то есть dll с реализацией, а сами мы при импорте можем лишь работать с интерфейсом и как-то его создавать.
Впрочем, даже без COM можно сделать отдельную динамическую библиотеку, в которой будет реализация интерфейса, а сами мы будет иметь лишь C++-овый интерфейс с помощью хедера. И вообще, зачем в примере нужна статическая библиотека, если можно просто через extern функцию в хедере то же самое?
Но хочу сказать, идея интересная. Я так понимаю, что конечному пользователю мы просто шлём различные версии либы, а сам хедер пишем универсальный, как и саму программу — действительно интересно. Впрочем, естественно, в одном файле все архитектуры не учесть — так что не могу быть уверенным в полезности данной техники, если эта самая либа не будет использоваться у конечного пользователя прямо чтобы много где.
Скажу больше субъективно, но всё же думаю, что если судить больше по общепринятым практикам, чем по определениям и принципам, то это всё же ближе к функциональному или типичному императивному программированию, чем к ооп.
Немного объясню, что имею ввиду:
В (типичном) ООП-стиле обычно принято реализовывать все функции "внутри" и использовать абстракции (интерфейсы):
interface ISomething{
void DoSomething(IAnother that);
}
sealed class Smth1 : ISomething {
public void DoSomething(IAnother that) {/*…*/}
}
abstract class Smth2 : ISomething {
int i;
public abstract void DoSomething(object that);
public int DoAnother() {/*…*/}
}
Грубо говоря, интерфейс эквивалентен структуре, содержащей несколько readonly-полей-функций, а класс эквивалентен экземпляру этой структуры.
readonly record struct ISomething(
Action<IAnother> DoSomething
);
readonly struct Smth1{
public ISomething as_interface{get;}
}
//без приватных полей
readonly record struct Smth2{
public ISomething as_interface{get; init;}
public Func<int> DoAnother{get;},
}
Грубо говоря, подход в том, чтобы задать интерфейс и инстанцировать его, а классы — это некие шаблоны, как именно инстанцировать данный интерфейс.
С другой стороны, альтернативный подход, более принятый в обычном таком классическом процедурном программировании, как раз больше про то, чтобы задавать реализацию вне самого типа.
Грубо говоря, нечто, существующее ещё с самого C с тех пор когда был придуман switch{case: /**/}
В какой-то мере, конечно, да — сами интерфейсы/трейты/тайпклассы сами по себе являются енумами/юнионами с потенциальном бесконечным количеством вариантом, но как бы именно различие подхода в том, что в случае классических union, enum или sealed interface перебрать все варианты является полностью возможным, а в классическом (не-sealed) ООП возможно лишь взять и предположить класс испольщуя rtti, при том, возможно, не угадать.
Если что, я лишь больше философствую и в реальности в очень многих языках всё же используется смешанный подход — не всегда удобен ни самый верный условному solid ООП, ни самый неабстрактный классический подход.
И, безусловно, java молодцы, что добавили некое подобие дискриминированных юнионов к себе, даже при том, что язык вроде как любящий хранить консервативность.
Но разве до тех пор, пока мы не работаем с ∞, ⊥ или делением на 0, колёса не будут работать как и классическая алгебра?
Ну, то есть вроде бы, в отличие от классики,
, то есть требуется дополнительное
, но ведь как бы и стоит понимать, что
, то есть как бы
, что, впринципе, по итогу, противоречит сказанному.
Ну или, банально, нет препятствий просто посчитать прямо:
Из всего, что можно говорить о бесконечности, колёса мне кажутся самыми красивыми, ибо они полностью определены аксиоматически, при этом в них нет пресловутой "неопределённости", а так же не используют странные понятия вроде "бесконечно больших/малых" или пределов. И, сравнивая с похожими системами, эта кажется самой какой-то реалистичной, как бы не хотелось говорить о "реализме" в отношении математики.
Можно меня поправить или дополнить, если что.
Дополнение:
Хотя, думаю, ту формулу можно переписать как , так что нет необходимости вставлять влево лишнее слагаемое и это можно можно сделать на этаме суммы произведений…
мне до сих пор была интересна эта серия статей, так что я бы подождал одну про синхронизации, спасибо.
кроме раста я наверное не знаю языков, в которых к синхронизации как-то принуждают (но я не особо и разбирался в теме), так что также будут интересны и сравнения (в расте же вроде есть трейты
SyncиSend, которые мешают передавать значения нарушающие синхронизацию в другой поток — даже если разымплементировать их надо вручную для чего-то самописного; и чтобы взять ссылку из значения под мьютексом, нужно его залочить, если память не изменяет)в той же java хочешь пиши
synchronized-блоки, хочешь нет, вроде как, а плюсы как всегда позволяют тебе делать что хочешь, но по итогу слишком уж на отвали. (но опять же, я не очень прямо хорошо погружён, я лишь студент, который многопоток использовал лишь пару раз, так что могу ошибаться)----
по поводу конструкторов:
в расте у полей значения по умолчанию запрещены, и каждое поле нужно обязательно инициализировать; чтобы же получить значение по умолчанию нужно использовать
Default::default()или какой-нибудь_::new(), а чтобы проинициализировать напрямую нужноуказать значение для кпждого поля по отдельности.противоположным является подход например в C, Java или C#, где если не указать значение по умолчанию, то выберется конструктор по умолчанию, null или т.п. Конечно, в C# есть required, который правда всё равно для части случаев будет так себе, а в плюсах можно написать какой-то собственный простенький тип, в котором будет что-то вроде
, но как будто это костыли, о которых разработчики вряд ли будут думать (а может и будут, я не очень знаю).
подход аргентума я вижу похожим, но хоть немного лучшим в том, что значение по умолчанию нужно выбрать самим.
заодно синтаксис value.{/*lambda*/} мне напоминает что-то похожее в сишарпе или джаве (хотя наверное в котлине оно и вовсе будет околоидиоматичным, хотя и не уверен):
впринципе, в C# (и Rust тоже вроде как) также можно и сделать extension-метод, чтобы работал так же как
alsoв котлине, но всё равно котлин в этом плане как будто красивейший, и подход из аргентума мне напоминает его, тем более что это ещё и идёт по умолчанию — по дизайну решение безусловно красивое.ну а так, мне тоже нравится будто бы повсеместный отказ от конструкторов (и от перегрузок функций в частности)
просто в аргентуме значение по умолчанию будто бы словно есть абсолютно всегда, если я правильно понимаю, что не во всех ситуациях полезно (условно не существует же никакого сокета или дескриптора файла по умолчанию — верно? а если и есть, то стоит считать, что он должен вести себя равно какому-нибудь null, не так ли?)
по поводу классов: от того, что всегда есть значения по умолчанию, всё хорошо будет, не в этой модели, а в общем случае? и не будет ошибок с тем, что потенциально можно пропустить инициализацию какого-то поля с данной моделью их инициализации? хотя понимаю, что более старые языки тоже многие к этому склонны, но всё-таки.
и если тут утверждается, что тут счёт ссылок неатомарный, то что будет в многопотоке?
есть ли какая-то гарантия что я не протащу слабую ссылку в другой поток имея возможность редактировать основную из этого?
последний абзац я уже не понимаю, ибо так всей этой теорией не интересовался, но я просто думаю, что для N-мерного пространства можно определить N-мерный тензор, который как бы будет результатом перемножения 0 элементов, и затем уже получать то что нужно как было сказано через тензорное произведение на векторы-множители
чисто технически, есть ведь ещё и типы-степени (функции)… сосредотачиваться лишь на суммах и произведениях — не так хорошо, я полагаю, но как бы с другой стороны, функции должны быть даже в тех япах, где нету структур… немного спускаясь в полуфилософскую шутливость, операции открылись словно наоборот от классической алгебры. если конечно не вспоминать о first class – функциях и замыканиях…
В этом есть какие-то минусы кроме нарушения около-стереотипа о том, что раз умножение, то обязательно именно двух элементов?
К тому же такое умножение 4 векторов всё равно работало бы также как и для 3д-умножения — результатом будет пятый, вектор перпендикулярный этим четырём, при том что его длина будет соответвовать площади гиперпараллелепипеда между ними. Как минимум, поиск перпендикулярного вектора это задача очень часто реально полезная.
да и если и рассматривать только пространства где произведения могут быть только от двух элементов, с этим же вопросом было бы интересно подойти по поводу того, как понять для ккких пространств определено произведение от n элементов для произвольного неотрицательного целого n. если условно произведение от 1 элемента определено для каждого пространства, от 2 лишь для 0, 1, 3, 7, от 0 при беглом обдумывании непонятно как определять кроме как для 1- и 0-мерного пространства — и вот это типа обобщить для произвольного фиксированного числа множителей.
знаю это, просто посчитал кодпоинты за символы
так-то да, просто я посчитал управляющие символы так же за символы.
но если делать именно так, то для условного о̴̷̰͙͓̰ͫ͗̉͜ͅ никаких 32 бит не хватит.
при том, для часто используемых сочетаний символ-лигатура (ё, だ, ã, …) и так есть отдельные кодпоинты. отдельно взятые лигатуры ведь нужны именно когда нужно олигатурить произвольный символ, нет? (хотя отдельно взятого э́ какого-нибудь не хватает или отдельных кодпоинтов для разных вариаций одного символа (привет, cjk) — но тут скорее вопрос к создателям юникода)
UTF-32 разве как раз этого не делает?
[[maybe_unused]]добавили в C++17. Даже до этого, если нет согласия с линтером и есть достаточная уверенность, что он брешет, вариант с(void)или любой другой подобной глушилкой был бы не так плох.Собственно, я впринципе с точки зрения линтера не особо понимаю, в чём проблема, если переменная не использовалась в одной из веток — важнее же чтобы использовалась хотя бы в одной, разве нет?
Я не особо математик и могу чего-то не знать, поэтому скажу лишь свои мысли как обывателя.
Как будто векторное произведение определёно на любой мерности, просто придётся работать с тензорами, а не векторами.
Но вообще, тогда лучше впринципе определить не тензор как результат век. произведения двух векторов, а тензор как век. произведение ноля вектороа — а потом домножать вектора на этот тензор. — То есть, определить некий тензор исходя из самого пространства.
Но вообще, если не лезть в тензоры, можно просто сказать, что век. произведение в ланной мерности N определено для N - 1 векторов.
Способ считать век. произведение как детерминант матрицы, где самая верхняя строчка это единичные векторы очень легко обобщается до многомерных пространств. Я этим в школе для своих развлечений вроде как типа и пользовался.
это не так? в 4-мерном пространстве двум векторам может быть перпендикулярна плоскость — не значит ли это, что любой вектор между двумя точками этой плоскости будет перпендикулярен изначальному вектору?
jit-компилятор мб смог бы, а вот aot только если в несколько проходов. ну или сделать какую-то статическую переменную на случай "ладно, не хочу". но не думаю, что aot-компилятороделы будут использовать такой динамический подход.
Может быть, всё же дело в том, что я не джавист и джавовские практики мне не знакомы. Если так рассудить, явные исключения действительно схожи с
Result, а неявные сpanic. Но, в какой-то мере, мне кажется, что проблема исключений в некоторой степени и в том, что ты с ними работать будешь как с исключениями. Возможно, было бы удобно всё же сделать какой-тоResultтипа такого, чисто чтобы что-то делать локально внутри функции и локально с результатом конкретной функции — хотя это, наверное, и грозит потерей стектрейса (или нет, я не помню, как это работает).Во всяком случае, я думаю, что есть пару вещей, которые изменить бы хотелось, чтобы всё работало приятнее:
Синтаксис
Условный
try{ } catch(ExceptionType e) {}лично для меня имеет пару проблем в дизайне:1) Фигурные скобки вокруг try и catch обязательные — рассчитаны лишь на то, чтобы оборачивать всю функцию — при необязательности для остальных конструкций.
2) Тип исключения — если throws функции лишь одного типа, тип в catch можно бы и вывести из контекста
3) Блок try всегда возвращает void — нужно заводить переменную вне блока для результата.
4) Сахар на
try{ } catch(ExceptionType e) {throw new RuntimeException(e)}всё же был бы приятенНеявность и схожесть с неявными исключениями — поставь
throws Exceptionна функцию — и не заметь throw там, где ты уже хочешь его поймать.Не помешали бы существующие отдельно методы, кидающие явные и неявные исключение в тех случаях, когда принято кидать лишь неявные.
Неудобность с точки зрения типизации. Опять же, условный
Function<A, B>уже запрещает кидать явные эксепшены. Если бы явные исключения становились частью возвращаемого типа, было бы удобнее. Хотя и можно сделать условныйThrowingFunction<ArgT, ResT, ExT extends Exception>, навроде того, что я написал сверху, но так-то большинству библиотек и фреймворков всё же будет всё равно.Но может быть и такое, что это всё вообще не нужно, всё и так работает замечательно, я не прав и у меня мозг просто немного оrustенел, так сказать.
Если проблема в невидимых загрузках, то можно с C++23 вот так сделать:
И вызывать как обычный метод.
Я сам не джавист, но всё же условно в вузе с джавой мы работали и за это время я заметил маленький минус у такого подхода. Условно я не могу написать так:
По этому мне придётся оборачивать:
Я всё же считаю, что такое оборачивание является некоторой проблемой и не так далеко ушло от какого-то
err != nilв go — хоть и не нужно так часто.Предположу, что отличие в том, что речь идёт о том, что инкапсуляция будет происходить на уровне файлов, а не на уровне линковки.
То есть условно если в классике, конечный пользователь скачает сразу файл (jar/dll/…), где будет уже динамически выбираться реализация, то здесь, я так понял, речь о том, чтобы на уровне конечного пользователя скачивать файл с реализацией в зависимости от цели.
Интересно, именно поэтому был придуман COM? — условно у нас в файлах где-то есть dll с реализацией, а сами мы при импорте можем лишь работать с интерфейсом и как-то его создавать.
Впрочем, даже без COM можно сделать отдельную динамическую библиотеку, в которой будет реализация интерфейса, а сами мы будет иметь лишь C++-овый интерфейс с помощью хедера. И вообще, зачем в примере нужна статическая библиотека, если можно просто через extern функцию в хедере то же самое?
Но хочу сказать, идея интересная. Я так понимаю, что конечному пользователю мы просто шлём различные версии либы, а сам хедер пишем универсальный, как и саму программу — действительно интересно. Впрочем, естественно, в одном файле все архитектуры не учесть — так что не могу быть уверенным в полезности данной техники, если эта самая либа не будет использоваться у конечного пользователя прямо чтобы много где.
По виду, это C++ с майкрософтовскими дополнениями — но даже так, есть странные моменты. Может, псевдокод.
Скажу больше субъективно, но всё же думаю, что если судить больше по общепринятым практикам, чем по определениям и принципам, то это всё же ближе к функциональному или типичному императивному программированию, чем к ооп.
Немного объясню, что имею ввиду:
В (типичном) ООП-стиле обычно принято реализовывать все функции "внутри" и использовать абстракции (интерфейсы):
Грубо говоря, интерфейс эквивалентен структуре, содержащей несколько readonly-полей-функций, а класс эквивалентен экземпляру этой структуры.
Грубо говоря, подход в том, чтобы задать интерфейс и инстанцировать его, а классы — это некие шаблоны, как именно инстанцировать данный интерфейс.
С другой стороны, альтернативный подход, более принятый в обычном таком классическом процедурном программировании, как раз больше про то, чтобы задавать реализацию вне самого типа.
Грубо говоря, нечто, существующее ещё с самого C с тех пор когда был придуман
switch{case: /**/}В какой-то мере, конечно, да — сами интерфейсы/трейты/тайпклассы сами по себе являются енумами/юнионами с потенциальном бесконечным количеством вариантом, но как бы именно различие подхода в том, что в случае классических
union,enumилиsealed interfaceперебрать все варианты является полностью возможным, а в классическом (не-sealed) ООП возможно лишь взять и предположить класс испольщуя rtti, при том, возможно, не угадать.Если что, я лишь больше философствую и в реальности в очень многих языках всё же используется смешанный подход — не всегда удобен ни самый верный условному solid ООП, ни самый неабстрактный классический подход.
И, безусловно, java молодцы, что добавили некое подобие дискриминированных юнионов к себе, даже при том, что язык вроде как любящий хранить консервативность.
Но разве до тех пор, пока мы не работаем с ∞, ⊥ или делением на 0, колёса не будут работать как и классическая алгебра?
Ну, то есть вроде бы, в отличие от классики,
, то есть требуется дополнительное
, но ведь как бы и стоит понимать, что
, то есть как бы
, что, впринципе, по итогу, противоречит сказанному.
Ну или, банально, нет препятствий просто посчитать прямо:
Из всего, что можно говорить о бесконечности, колёса мне кажутся самыми красивыми, ибо они полностью определены аксиоматически, при этом в них нет пресловутой "неопределённости", а так же не используют странные понятия вроде "бесконечно больших/малых" или пределов. И, сравнивая с похожими системами, эта кажется самой какой-то реалистичной, как бы не хотелось говорить о "реализме" в отношении математики.
Можно меня поправить или дополнить, если что.
Дополнение:
Хотя, думаю, ту формулу можно переписать как
, так что нет необходимости вставлять влево лишнее слагаемое и это можно можно сделать на этаме суммы произведений…
Дополнение 2:
Нет, нельзя. Потому что
, что, по идее, должно быть уже верным.
К тому, что
, нужно ещё привыкнуть…