"Максимально" это конечно перегиб, всё же malloc гарантированно выровнен по выравниванию наиболее выровненного встроенного сишного типа (обычно выровнен в два машинных слова), чего просто атомикам, конечно, и хватит, но всё же для гарантий в более общем случае лучше использовать aligned_alloc, например при выравнивании по ширине кеш-линии, как иногда делается
Никакого нюанса нет, можно ограничение возможных значений считать просто инвариантом над u8, да и если захочется, всё ещё есть способы упаковать под ситуацию.
в расте это как будто окончательно решили тем, что запретили = возвращать что либо кроме (). как по мне, само то, что присваивание возвращает ссылку является стрельбой в ногу, как и возможность типам быть конвертируемыми в bool кроме пары случаев когда тип действительно близок по смыслу навроде bool? в C# ну или, может быть, любых option-типов
я не говорил о какой-то пользе трюка. это и есть просто цикл, записанный через рекурсию
собственно, суть лишь в том, что всё можно записать через циклы и мутабельные переменные, можно аналогично реализовать с неизменяемыми переменными и хвостовой рекурсией — вопрос лишь в тяжести (да и в любом случае, компилятор это обратно соптимизирует в обычный цикл)
не буду говорить, что в этом есть хоть какая-то польза с точки зрения исполнения кода, но как нечто на подумать — это может быть забавным.
да и просто то, что рекурсия написана нехвостовая, когда её почти тривиально можно сделать таковой, всё равно кажется не самой приятной вещью
немного странно, учитывая, что что факториал, что возведение в степень легко переписать через хвостовую рекурсию (хоть, конечно, и потребуется от компилятора её поддержка — но на gcc или clang это просто один флаг поставить)
далее планируемые числа фибоначи тоже
хотя вот числа аккермана и определитель без введения какого-то списка на куче кажутся тяжёлыми в этом плане (хотя я никогда не писал реализацию для аккермана хоть какую, так что знать не могу… вообще, это наверное просто вопрос того, чтоб немного поразмыслить)
в любом случае, для восстановления справедливости, не было бы неинтересным видеть тоже самое что писалось через "адекватные" циклы, написанным через tailrec
(впрочем, всё же очевидным минусом тут является в любом случае, что компилятор волен поступить как захочет)
мне до сих пор была интересна эта серия статей, так что я бы подождал одну про синхронизации, спасибо.
кроме раста я наверное не знаю языков, в которых к синхронизации как-то принуждают (но я не особо и разбирался в теме), так что также будут интересны и сравнения (в расте же вроде есть трейты 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енел, так сказать.
"Максимально" это конечно перегиб, всё же
mallocгарантированно выровнен по выравниванию наиболее выровненного встроенного сишного типа (обычно выровнен в два машинных слова), чего просто атомикам, конечно, и хватит, но всё же для гарантий в более общем случае лучше использоватьaligned_alloc, например при выравнивании по ширине кеш-линии, как иногда делаетсяНикакого нюанса нет, можно ограничение возможных значений считать просто инвариантом над
u8, да и если захочется, всё ещё есть способы упаковать под ситуацию.в расте это как будто окончательно решили тем, что запретили
=возвращать что либо кроме(). как по мне, само то, что присваивание возвращает ссылку является стрельбой в ногу, как и возможность типам быть конвертируемыми вboolкроме пары случаев когда тип действительно близок по смыслу навродеbool?в C# ну или, может быть, любыхoption-типовя не говорил о какой-то пользе трюка. это и есть просто цикл, записанный через рекурсию
собственно, суть лишь в том, что всё можно записать через циклы и мутабельные переменные, можно аналогично реализовать с неизменяемыми переменными и хвостовой рекурсией — вопрос лишь в тяжести (да и в любом случае, компилятор это обратно соптимизирует в обычный цикл)
не буду говорить, что в этом есть хоть какая-то польза с точки зрения исполнения кода, но как нечто на подумать — это может быть забавным.
да и просто то, что рекурсия написана нехвостовая, когда её почти тривиально можно сделать таковой, всё равно кажется не самой приятной вещью
Нет, это то же самое
я её уже однажды придумав написал, и потому легко повторю ещё раз :)
можно даже легко переписать для случаев, когда n < 0
по сути, все аргументы это просто всё те же мутабельные переменные, если бы мы писали через обычные циклы
немного странно, учитывая, что что факториал, что возведение в степень легко переписать через хвостовую рекурсию (хоть, конечно, и потребуется от компилятора её поддержка — но на gcc или clang это просто один флаг поставить)
далее планируемые числа фибоначи тоже
хотя вот числа аккермана и определитель без введения какого-то списка на куче кажутся тяжёлыми в этом плане (хотя я никогда не писал реализацию для аккермана хоть какую, так что знать не могу… вообще, это наверное просто вопрос того, чтоб немного поразмыслить)
в любом случае, для восстановления справедливости, не было бы неинтересным видеть тоже самое что писалось через "адекватные" циклы, написанным через tailrec
(впрочем, всё же очевидным минусом тут является в любом случае, что компилятор волен поступить как захочет)
мне до сих пор была интересна эта серия статей, так что я бы подождал одну про синхронизации, спасибо.
кроме раста я наверное не знаю языков, в которых к синхронизации как-то принуждают (но я не особо и разбирался в теме), так что также будут интересны и сравнения (в расте же вроде есть трейты
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 вот так сделать:
И вызывать как обычный метод.