Здравствуйте, Sinclair, Вы писали:
V>>Где только возможно стоит использовать readonly-модификатор метода, ReadOnlySpan. S>А зачем? Это внутренний код, я и так могу убедиться, что я при поиске индексы не меняю.
При использовании readonly-модификаторов методов и readonly ref / in ссылок зачастую генерируется другой код.
Попробуй поиграть с этими вещами и посмотреть на выход JIT.
В дотнете порой неочевидно, когда он создаёт копии структур для вызова методов.
S>Кажется, я понял, в чём дело. JIT отказывается инлайнить рекурсию.
S>и понимает, что N::get_Item — это тоже BranchNode<..., ...>::get_Item, то несмотря на то, что это совсем другой метод
Метод тот же, просто аргументы генерика другие.
Т.е. в любом случае рекурсия.
S>Надо бы найти это место в исходниках JIT и посмотреть, можно ли доработать детектор рекурсии.
А как ты её доработаешь?
Кстате, я не раз бодался с рекурсивными генериками в дотнете, грубо на пальцах:
public unsafe interface IByteBuffer
{
ReadOnlySpan<byte> Span { get; }
Span<byte> Allocate(uint space);
void AppendChar(byte chr);
void Append(ReadOnlySpan<byte> bytes);
void Append(byte * bytes, uint length);
// removes last n bytesvoid ShrinkBy(uint n);
void Reset();
}
public interface ITextBuffer : IByteBuffer
{
/// Changes current indentation (positive - increment, negative - decrement)void Indent(int delta);
/// Resets current indent to 0void ClearIndent();
/// Starts new line with current indentationvoid NewLine();
}
public interface ITextable {
[Pure]
void RenderTo<TBuffer>(ref TBuffer buffer)
where TBuffer : struct, ITextBuffer;
}
public interface ILogMsg
{
[Pure]
LogLevel Severity { get; }
[Pure]
string Facility { get; }
[Pure]
void RenderTo<TTextBuffer>(ref TTextBuffer tb)
where TTextBuffer : struct, ILogFormatter;
}
public readonly struct LogMsgChain<TCh, T> : ILogMsg
where TCh : struct, ILogMsg where T : struct, ITextable
{
private readonly TCh chain;
private readonly T value;
public void RenderTo<TBuffer>(ref TBuffer tb)
where TBuffer : struct, ILogFormatter {
chain.RenderTo(ref tb);
value.RenderTo(ref tb);
}
public static LogMsgChain<LogMsgChain<TCh, T>, StringFormatter> operator%(
in LogMsgChain<TCh, T> prev, string val)
=> new LogMsgChain<LogMsgChain<TCh, T>, StringFormatter>(in prev, val);
public static LogMsgChain<LogMsgChain<TCh, T>, IntFormatter> operator
%(in LogMsgChain<TCh, T> prev, int val)
=> new LogMsgChain<LogMsgChain<TCh, T>, IntFormatter>(in prev, val);
...
public static class LoggerExtensions
{
public static void Log<TLogger, TMsg>(this ref TLogger logger, in TMsg msg)
where TLogger : struct, ILogger
where TMsg : struct, ILogMsg
{
if(logger.ShouldLog(in msg))
logger.ForceLog(in msg);
}
...
В разных местах кода получаются примерно такие типы:
LogMsgChain<LogMsgChain<LogMsgChain<LogMsg, StringFormatter>, IntFormatter>, StringFormatter>
logger.Log(Log.Trace[Component] % "received " % result % " bytes");
(сетка форматтеров большая, любые новые прикладные типы описывают форматтеры для себя для вывода в лог)
Это калька с плюсового логгера, там LogMsgChain.RenderTo инайнится целиком, а в дотнете только до определённой глубины рекурсивного типа, затем chain.RenderTo(ref tb) вызывается обычным образом.
Это очередной камешек в сторону АОТ vs JIT.
Джиту просто некогда заглядывать слишком глубоко.
Поэтому, непонятно, что ты хотел посмотреть-подкрутить в исходниках джита?
Увеличить пороги счётчиков сложностей, чтобы джит тратил больше времени на компиляцию? ))
S>Варианты, как это обойти, не фикся джит: S>- вернуться к варианту с циклом. Он, в целом, был хорош всем, кроме однотипности узлов. В итоге у меня внутри листьев тратилось место на индексы, увеличивая потребление памяти, и ухудшая характеристики всех модифицирующих операций.
Ну вот почитай по моей ссылке инфу — я когда-то плотно гонял различные архитектуры реализаций map (IDictionary в дотнете), в виде деревьев и хеш-таблиц.
Дефолтные реализации хеш-таблиц хорошо ведут себя только при чудовищном перерасходе памяти, а не дефолтные порой специфические, могут быть дорогими в набивке хеш-таблице, хотя хорошо борются с основными косяками дефолтных реализаций хеш-таблиц.
В общем, map на деревьях показывает самое устойчивое быстродействие, т.е. независимо от характера фактических данных (в отличие от хеш-таблиц, которые к характеру данных слишком чувствительны).
S>- развернуть рекурсию, добавив 2-3-4 типа BranchNode1, BranchNode2, которые будут отличаться только методом SplitNGrow. Тогда медленный код будет вызываться только при очень больших глубинах дерева (например, миллион элементов требуют всего-то 4х уровней).
Это если дерево сбалансировано.
Без балансировки дерево может быть профанировано фактическими данными, может выродится в линейный список.
(в твоём случае длина списка будет N/16)
Т.е., если у тебя балансировки еще нет, то можно сократить арность и ввести стадию балансировки.
Заодно отпадут индексы, т.е. операции станут резко дешевле.
А дешевизна операций в случае дерева стоит при показателе степени, т.е. в случае деревьев вааще нифига не пустой звук.
Т.е., удвоив быстродействие, получаем квадрат от основания (сейчас у тебя 16) раз потенциального увеличения элементов при сохранении прежнего среднего времени доступа.
Для бинарных деревьев при удвоении быстродействия можно хранить с прежней эффективностью в 4 раза больше данных и т.д.
Re[55]: MS забило на дотнет. Питону - да, сишарпу - нет?
Здравствуйте, vdimas, Вы писали:
V>А там разве не рекурсия? V>(руки не дошли взять полный код, пока справшиваю словами)
Неа. Там цепочка типов. Внизу лежит LeafNode<int>, над ними — BranchNode<LeafNode<int>, int>, над ними — BranchNode<BranchNode<LeafNode<int>, int>, int>, и так далее. V>В крайнем случае это можно сделать ручками, но при малой фактической вложенности (единицы десятков, те самые 20-30 максимум) смысла обычно нет. V>И да, я когда-то более 15 лет назад сравнивал бинарные деревья с N-арными, ну вот 2-4 элемента в узле давали максимум производительности, т.е. увеличение кол-ва элементов в узле ничего не давало. Гораздо больше дал кастомный аллокатор узлов, но вряд ли это поможет в случае дотнета.
Ну вот 16 мне дали объехать и 2, и 8 элементов. На подробное сравнение производительности различных размеров узлов времени не хватило.
V>(Кстате, не увидел у тебя балансировки, но может не весь код смотрел)
Там она просто выкинута, чтобы sharplab.io не тормозил. В исходниках она, естественно, есть.
V>Еще, если заменить в твоей реализации _dataX на inplace-массив, то через одну константу арности можно легко менять кол-во элементов в узле, т.е. задешево сравнить сетку реализаций с отличающейся арностью.
Не получится, т.к. inplace-массив бывает только встроенных типов.
V>- хеш-таблица в хеш-таблице (т.е. любой конфликт оборачивается в хеш-таблицу следующего подуровня и т.д. рекурсивно, т.е. тоже получается дерево, просто большой арности узлов, и размер подтаблицы не может быть равен размеру родительской таблицы, т.е. это должно быть другое простое число); V>- дефолтные реализации (занятие соседних пустых ячеек); V>- в хеш-таблице по данному хеш-коду содержится первый элемент и опционально указатель на сортированный список остальных конфликтных элементов. V>Последний вариант показал себя самым эффективным для большинства случаев. V>Второй вариант для сравнимой эффективности требует большого запаса по размеру ячеек хеш-таблицы относительно данных, т.е. с приличным превышением. V>Первый вариант оказался самый тормозной, хотя всё тестирование ради него затевалось, т.к. оно хорошо подходит под иммутабельные сценарии.
Не очень понятно, как делать иммутабельный сценарий на хеш-таблице. Ведь каждая вставка должна порождать копию — а это очень дорого для мало-мальски заметных объёмов таблицы. V>Самое время проверить еще раз этот вариант на актуальной технике и актуальных компиляторах-джитах, сравнить с честными деревьями, типа как у тебя.
V>Но в хеш-таблицах есть еще любопытный способ их построения — через подбор коэфициентов хеш-функции. V>На пальцах — существует несколько классов хеш-функций, в которых через параметры можно управлять "законом" разброса. V>Наполнение такой таблицы дорогое, т.к. в процессе наполнения происходит подбор коэфициентов хеш-функции для минимизации конфликтов. V>Но зато при последующем чтении данных такие таблицы показывают хорошие результаты практически при любых сценариях, например даже в таких, где классические хеш-таблицы с фиксированными хеш-функциями из-за стечения обстоятельств (вернее, данных) вырождаются в своей эффективности до эффективности только механизма, разруливающего конфликты. V>Т.е. когда классические (дефолтные) реализации хеш-таблиц де-факто профанируются.
Стандартом в наши дни является случайное подсаливание хеша. То есть в пределах запуска VM одинаковые данные получают одинаковый хеш, но после перезапуска — уже нет.
Всё это потому, что случайно нарваться на такое распределение данных, которое перегружает хеш, маловероятно. Если у нас обнаружился перекос — то скорее всего какой-то добрый человек выполняет DOS-атаку, запихивая к нам в API особо подобранные данные. Соль позволяет это предотвратить, и при этом не ухудшает стоимость хеширования.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[54]: MS забило на дотнет. Питону - да, сишарпу - нет?
Здравствуйте, vdimas, Вы писали: V>В дотнете порой неочевидно, когда он создаёт копии структур для вызова методов.
Да, я читал эти сложные правила. Поэтому у меня в коде нет ни readonly структур, ни in вызовов.
Полагаю, именно поэтому у Marshal.CreateReadOnlySpan нет in в сигнатуре — при передаче в него не-readonly структуры, дотнет будет делать protective copy внутри вызова, убивая всю производительность. У него нет аналога плюсового модификатора const для методов (кстати, странно, что нету — с учётом всех этих readonly модификаторов и in-параметров он выглядит прямо-таки необходимым).
V>Метод тот же, просто аргументы генерика другие. V>Т.е. в любом случае рекурсия.
Другие аргументы генерика — другое тело.
V>А как ты её доработаешь?
Это тоже вопрос. По-хорошему, если метод вызывается с другими value типами в генерик-параметрах, то это не рекурсия.
V>Кстате, я не раз бодался с рекурсивными генериками в дотнете, грубо на пальцах: V>В разных местах кода получаются примерно такие типы: V>LogMsgChain<LogMsgChain<LogMsgChain<LogMsg, StringFormatter>, IntFormatter>, StringFormatter> V>
V>logger.Log(Log.Trace[Component] % "received " % result % " bytes");
V>
V>(сетка форматтеров большая, любые новые прикладные типы описывают форматтеры для себя для вывода в лог)
Да, совершенно верно, это именно тот случай. V>Это калька с плюсового логгера, там LogMsgChain.RenderTo инайнится целиком, а в дотнете только до определённой глубины рекурсивного типа, затем chain.RenderTo(ref tb) вызывается обычным образом.
Как я понял, ровно до глубины 1. Можете немедленно проверить на шарплабе. Возможно, моя гипотеза неверна. V>Это очередной камешек в сторону АОТ vs JIT. V>Джиту просто некогда заглядывать слишком глубоко.
Нет, просто в нём ошибка. Для нерекурсивных вызовов он прекрасно заглядывает на N уровней вглубь — ровно до момента превышения ограничения на размер метода.
V>Поэтому, непонятно, что ты хотел посмотреть-подкрутить в исходниках джита? V>Увеличить пороги счётчиков сложностей, чтобы джит тратил больше времени на компиляцию? ))
Нет, изменить код детектора рекурсии.
S>>Варианты, как это обойти, не фикся джит: S>>- вернуться к варианту с циклом. Он, в целом, был хорош всем, кроме однотипности узлов. В итоге у меня внутри листьев тратилось место на индексы, увеличивая потребление памяти, и ухудшая характеристики всех модифицирующих операций.
V>Ну вот почитай по моей ссылке инфу — я когда-то плотно гонял различные архитектуры реализаций map (IDictionary в дотнете), в виде деревьев и хеш-таблиц.
По какой ссылке? V>В общем, map на деревьях показывает самое устойчивое быстродействие, т.е. независимо от характера фактических данных (в отличие от хеш-таблиц, которые к характеру данных слишком чувствительны).
В данном случае меня интересует не map, а ImmutableList<T>.
V>Это если дерево сбалансировано.
Конечно сбалансировано. Это же классическое B+-дерево.
V>Без балансировки дерево может быть профанировано фактическими данными, может выродится в линейный список. V>(в твоём случае длина списка будет N/16)
В моём случае глубина будет ограничена log(N, 8). Это очень мало для более-менее любых реалистичных int.
Я думал, это и так понятно из графиков в описании проекта.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[53]: MS забило на дотнет. Питону - да, сишарпу - нет?
Здравствуйте, Sinclair, Вы писали:
V>>Вариант на FindChildV, который немного подшаманил. S>Тоже не лучше — много вызовов в this[int]:
Это вызов локальной ф-ии Return, которая вызывается в нескольких местах.
Мне вообще не нравится решение с отдельным Bunch _indices — делаются лишние проверки лишнего Span.
Можно объединить индексы с данными в одной структуре.
И не нравится разделение узлов на листья и ветки — вводится виртуальность.
Здравствуйте, Sinclair, Вы писали:
S>>Ну и на самом деле SG может выступать как специализация из аля шаблона С++. То есть максимально приблизить к нативному коду. S>>Ну и учитывая Preview Features in .NET 6 – Generic Math S>>Еще легче переносить шаблоны С++ S>Какие именно шаблоны хочется перенести?
Кстати я тут уже писал
В Delphi есть возможность объявить за реализацию класса свойство класса реализующего этот интерфейс (Implements). https://www.delphiplus.org/programirovanie-v-srede-delphi-for-net/direktiva-implements.html
Но по сути это аналог множественного наследования в C++
Вот такая возможность с возможностью переопределения методов с возможностью вызова base была бы интересна.
Кстати с помощью Source Generator это легко сделать!
То есть такой класс можно наследовать через
class StringAccount : Account<string>
{
public StringAccount(string id) : base(id)
{
}
}
Здравствуйте, Serginio1, Вы писали:
S>Ты давай отвечай чем в данном случае интерфейсы лучше и паттерн стратегия.
Вот, кстати, из педивикии:
Issues and alternatives
Implementation inheritance is controversial among programmers and theoreticians of object-oriented programming since at least the 1990s. Among them are the authors of Design Patterns, who advocate interface inheritance instead, and favor composition over inheritance. For example, the decorator pattern (as mentioned above) has been proposed to overcome the static nature of inheritance between classes. As a more fundamental solution to the same problem, role-oriented programming introduces a distinct relationship, played-by, combining properties of inheritance and composition into a new concept.
According to Allen Holub, the main problem with implementation inheritance is that it introduces unnecessary coupling in the form of the "fragile base class problem": modifications to the base class implementation can cause inadvertent behavioral changes in subclasses. Using interfaces avoids this problem because no implementation is shared, only the API. Another way of stating this is that "inheritance breaks encapsulation". The problem surfaces clearly in open object-oriented systems such as frameworks, where client code is expected to inherit from system-supplied classes and then substituted for the system's classes in its algorithms.
Reportedly, Java inventor James Gosling has spoken against implementation inheritance, stating that he would not include it if he were to redesign Java. Language designs that decouple inheritance from subtyping (interface inheritance) appeared as early as 1990; a modern example of this is the Go programming language.
Complex inheritance, or inheritance used within an insufficiently mature design, may lead to the yo-yo problem. When inheritance was used as a primary approach to structure code in a system in the late 1990s, developers naturally started to break code into multiple layers of inheritance as the system functionality grew. If a development team combined multiple layers of inheritance with the single responsibility principle it created many super thin layers of code, many which would only have 1 or 2 lines of code in each layer. Too many layers make debugging a significant challenge, as it becomes hard to determine which layer needs to be debugged.
Another issue with inheritance is that subclasses must be defined in code, which means that program users cannot add new subclasses at runtime. Other design patterns (such as Entity–component–system) allow program users to define variations of an entity at runtime.
Здравствуйте, Sinclair, Вы писали:
S>Не очень понятно, как делать иммутабельный сценарий на хеш-таблице. Ведь каждая вставка должна порождать копию — а это очень дорого для мало-мальски заметных объёмов таблицы.
В ФП языках для такого используют паттерн Memoize. Понятно, что внутри оно таки mutable, но снаружи свойство чистоты функции сохраняется. Вообще, важна не мутабельность, а именно чистота, а это все таки не всегда одно и тоже. Т.е. иммутабельный код всегда чистый, а вот обратное не верно.
... << RSDN@Home 1.3.17 alpha 5 rev. 62>>
Re[73]: MS забило на дотнет. Питону - да, сишарпу - нет?
Здравствуйте, Ночной Смотрящий, Вы писали:
НС>https://en.wikipedia.org/wiki/Inheritance_(object-oriented_programming)#Issues_and_alternatives
НС>Читать до состояния просветления.
Угу а кто тебе запрещает использовать и интерфейсы и наследование одновременно?
Зачем в интерфейсы ввели возможность реализации? https://docs.microsoft.com/ru-ru/dotnet/csharp/language-reference/proposals/csharp-8.0/default-interface-methods
И наследование интерфейсов тоже не нужно?
В любом случае можно использовать уже имеющуюся реализацию как тебе угодно вплоть до методов расширения.
Чем больше инструментов тем лучше! НС>Это палка с одним концом. Наследование и из классов надо выкидывать, а не добавлять его огрызок еще и в структуры.
Твое заявление, что наследование не нужно, обрубает одну из возможностей! Этим ты только обедняешь язык
Все можно и нужно использовать, но с умом!
и солнце б утром не вставало, когда бы не было меня
Re[56]: MS забило на дотнет. Питону - да, сишарпу - нет?
Здравствуйте, Sinclair, Вы писали:
V>>А там разве не рекурсия? V>>(руки не дошли взять полный код, пока справшиваю словами) S>Неа. Там цепочка типов. Внизу лежит LeafNode<int>, над ними — BranchNode<LeafNode<int>, int>, над ними — BranchNode<BranchNode<LeafNode<int>, int>, int>, и так далее.
Тогда на верхнем уровне в любом случае виртуальность.
V>>В крайнем случае это можно сделать ручками, но при малой фактической вложенности (единицы десятков, те самые 20-30 максимум) смысла обычно нет. V>>И да, я когда-то более 15 лет назад сравнивал бинарные деревья с N-арными, ну вот 2-4 элемента в узле давали максимум производительности, т.е. увеличение кол-ва элементов в узле ничего не давало. Гораздо больше дал кастомный аллокатор узлов, но вряд ли это поможет в случае дотнета. S>Ну вот 16 мне дали объехать и 2, и 8 элементов.
Что странно.
V>>(Кстате, не увидел у тебя балансировки, но может не весь код смотрел) S>Там она просто выкинута, чтобы sharplab.io не тормозил.
Попробую найти время, посмотреть на балансировку.
Точная балансировка тем дороже, чем больше арность, потому что приходится создавать более дорогие копии при каждом изменении дерева.
И еще вести учёт реально занятого кол-ва ячеек в узле.
А в бинарном дереве ячека содержит ровно одно значение, никакого учёта.
V>>Еще, если заменить в твоей реализации _dataX на inplace-массив, то через одну константу арности можно легко менять кол-во элементов в узле, т.е. задешево сравнить сетку реализаций с отличающейся арностью. S>Не получится, т.к. inplace-массив бывает только встроенных типов.
Да, потом увидел, что у тебя для произвольных T.
S>Не очень понятно, как делать иммутабельный сценарий на хеш-таблице. Ведь каждая вставка должна порождать копию — а это очень дорого для мало-мальски заметных объёмов таблицы.
Ну вот как раз ради случая хеш-таблицы в хеш-таблицах всё и затевалось.
Capacity каждой хеш-таблицы относительно невелик (23, 19, 17, 13, 11), пересоздавать при изменении достаточно одну таблицу, а не всю цепочку их до корневого узла, как оно есть в иммутабельных деревьях при балансировке.
Т.е., ввиду того, что вложенные хеш-таблицы имеют разный размер, балансировка отсутствует, т.к. такое хранилище не может выродиться в связанный список.
Эта структура всё-равно не подходит для реализации твоего упорядоченного по индексу списка, просто меня неприятно поразил сам факт проседания быстродействия в этой схеме.
S>Стандартом в наши дни является случайное подсаливание хеша. То есть в пределах запуска VM одинаковые данные получают одинаковый хеш, но после перезапуска — уже нет. S>Всё это потому, что случайно нарваться на такое распределение данных, которое перегружает хеш, маловероятно.
В дотнетной реализации тупо происходит рост capacity с последующим перекешированием (писал про этот случай, он второй в списке), т.е. capacity таблицы может быть во многие разы больше кол-ва реальных данных.
Для небольшого кол-ва данных это приемлимо, для большого нет.
Под большие наборы данных надо писать свои хеш-таблицы.
Самый простой вариант — складывать конфликты в упорядоченный список (массив конфликтующих значений).
Re[55]: MS забило на дотнет. Питону - да, сишарпу - нет?
Здравствуйте, Sinclair, Вы писали:
V>>В дотнете порой неочевидно, когда он создаёт копии структур для вызова методов. S>Да, я читал эти сложные правила. Поэтому у меня в коде нет ни readonly структур, ни in вызовов. S>У него нет аналога плюсового модификатора const для методов (кстати, странно, что нету — с учётом всех этих readonly модификаторов и in-параметров он выглядит прямо-таки необходимым).
Есть.
Для методов структур это readonly.
Для методов классов — это [Pure]
V>>(сетка форматтеров большая, любые новые прикладные типы описывают форматтеры для себя для вывода в лог) S>Да, совершенно верно, это именно тот случай. V>>Это калька с плюсового логгера, там LogMsgChain.RenderTo инайнится целиком, а в дотнете только до определённой глубины рекурсивного типа, затем chain.RenderTo(ref tb) вызывается обычным образом. S>Как я понял, ровно до глубины 1. Можете немедленно проверить на шарплабе. Возможно, моя гипотеза неверна.
Там много тащить для "немедленной проверки", но проверю как руки дойдут.
Re[67]: MS забило на дотнет. Питону - да, сишарпу - нет?
Здравствуйте, Ночной Смотрящий, Вы писали:
НС>>>Для полиморфизма — более чем достаточно. I>>Недостаточно, т.к. интерфейс не предоставляет никаких гарантий.
НС>Вообще никаких? К чему эта софистика? Очевидно что какие то гарантии он дает, но иммутабельность в этот список не входит.
Например в метод add суем логику обнуления контейнера. Какие именно гарантии предоставил интерфейс?
НС>Это пока. Хотя вон F# почти мейнстрим.
Я даже не помню, когда слышал про вакансии на нем.
I>>И когда некто понаимплементит в одном классе кучку интерфейсов, уследить за свойствами этого решения становится, мягко говоря, проблематично.
НС>У любой технологии есть свои недостатки. Вывод то какой? Не использовать ООП?
наоборот. Если ты понаимплементил интерфейсов это по факту отказ от ооп.
I>>Непонятно. Приведи пример для отношения is, subtype то есть.
НС>Зачем мне приводить какой то пример? Тебюе без примера непонятно, что связь базовый класс — наследник намного сильнее, чем интерфейс — реализация?
Непонятно, что ты имеешь ввиду. Ты только что сказал, что проблема в is, а потом согласился, что не в нем, а в совсем другом. Поэтому без примера не ясно, чего именно ты хочешь сказать.
I>>Ломаются гарантии. Ты получил ссылку с типом иммутабельного интерфейса и твой код полагается на это свойство иммутабельности.
НС>С чего бы? Гарантию иммутабельности ООП в принципе никогда не давал и не дает, хоть с интерфейсами, хоть без.
Успокойся — вот ты видишь, что интерфейс иммутабельный и в доке прямо сказано. И ты же автор интефейса. Твои действия? Как вызывать будешь, с учетом доки, своих намерений, или будешь додумывать "а на самом деле имплементор хочет обмануть"?
> Можно, конечно, что нибудь придумать, но я тогда тебя спрошу — а что насчет какой нибудь другой гарантии?
Давай не будем скакать и закончим с этой частью. Иммутабельность так же хороша как и любое другое свойство, которое нужно сохранить.
>, но отсутствие гарантии иммутабельности это далеко не единственный и даже не главный недостаток наследования классов.
С любыми гарантиями будет ровно то же.
I>>А у тебя выбора нет,
НС>У меня выбор есть
Так покажи этот выбор, как ты обеспечишь ту самую гарантию.
I>> т.к. в языках отсутствует проверка внятных гарантий. НС>А внятными гарантии кто назначает?
Как и везде — компилятор, дизайн, тесты и тд.
I>>Нужно заменить extends на subtype и все станет хорошо.
НС>Нет, все не станет.
Тогда показывай пример, если тебе есть чего сказать.
Re[74]: MS забило на дотнет. Питону - да, сишарпу - нет?
Здравствуйте, Ikemefula, Вы писали:
I>Например в метод add суем логику обнуления контейнера.
Кто и зачем сует в метод add логику обнуления контейнера?
I>>>И когда некто понаимплементит в одном классе кучку интерфейсов, уследить за свойствами этого решения становится, мягко говоря, проблематично. НС>>У любой технологии есть свои недостатки. Вывод то какой? Не использовать ООП? I> наоборот. Если ты понаимплементил интерфейсов это по факту отказ от ооп.
Нет.
НС>>Зачем мне приводить какой то пример? Тебюе без примера непонятно, что связь базовый класс — наследник намного сильнее, чем интерфейс — реализация? I>Непонятно, что ты имеешь ввиду.
Я тут рядом ссылочку дал. Там поподробнее.
НС>>С чего бы? Гарантию иммутабельности ООП в принципе никогда не давал и не дает, хоть с интерфейсами, хоть без. I>Успокойся
Я спокоен. Без демагогии никак?
I>- вот ты видишь, что интерфейс иммутабельный и в доке прямо сказано.
Нет, не вижу. Нет такого свойства у интерфейса, оно им ортогонально. Иммутабельность — это совершенно отдельная характеристика, и гарантий в этом плане со стороны шарпа крайне мало, хоть используй интерфейсы, хоть нет.
I>Давай не будем скакать
Скакать начал ты, притянув за уши иммутабельность. Я же который раз уже пишу очевидное — абсолютных гарантий ни одна конструкция языка не дает и дать не может.
I>>>А у тебя выбора нет, НС>>У меня выбор есть I>Так покажи этот выбор, как ты обеспечишь ту самую гарантию.
Ты пытаешься лишить меня выбора, зачем то навязывая мне потребности в гарантиях иммутабельности.
I>>> т.к. в языках отсутствует проверка внятных гарантий. НС>>А внятными гарантии кто назначает? I>Как и везде — компилятор, дизайн, тесты и тд.
И каким образом компилятор назначил иммутабельность внятной, а контроль на null — нет? Можешь пояснить?
I>>>Нужно заменить extends на subtype и все станет хорошо. НС>>Нет, все не станет. I>Тогда показывай пример, если тебе есть чего сказать.
Чайники Рассела полетели? Это ты давай доказывай, что subtype проблему решит.
... << RSDN@Home 1.3.17 alpha 5 rev. 62>>
Re[54]: MS забило на дотнет. Питону - да, сишарпу - нет?
Здравствуйте, Serginio1, Вы писали: S>Кстати я тут уже писал S> В Delphi есть возможность объявить за реализацию класса свойство класса реализующего этот интерфейс (Implements). S>https://www.delphiplus.org/programirovanie-v-srede-delphi-for-net/direktiva-implements.html
Я в курсе S>Но по сути это аналог множественного наследования в C++
По сути это встроенная поддержка агрегации в COM.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[57]: MS забило на дотнет. Питону - да, сишарпу - нет?
Здравствуйте, Ночной Смотрящий, Вы писали:
НС>В ФП языках для такого используют паттерн Memoize. Понятно, что внутри оно таки mutable, но снаружи свойство чистоты функции сохраняется.
Как я понял, в ФП языках как раз используют специализированные коллекции, с эффективной реализацией иммутабельности.
Я плохо знаком с миром ФП, и пока что до меня не доходит, каким волшебным образом memoize поможет сделать из мутабельной хеш-таблицы эффективную иммутабельную.
Вообще, важна не мутабельность, а именно чистота, а это все таки не всегда одно и тоже. Т.е. иммутабельный код всегда чистый, а вот обратное не верно.
Ну, я решаю одну узкую задачу: улучшить производительность System.Collections.Immutable.ImmutableList.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[57]: MS забило на дотнет. Питону - да, сишарпу - нет?
Здравствуйте, vdimas, Вы писали:
V>Тогда на верхнем уровне в любом случае виртуальность.
Нет конечно, не в любом. Все эти типы — struct, так что в коде нет ни одного виртуального вызова.
V>Что странно.
V>Попробую найти время, посмотреть на балансировку.
Да она там совершенно обычная, как в учебнике. V>Точная балансировка тем дороже, чем больше арность, потому что приходится создавать более дорогие копии при каждом изменении дерева.
Да, одиночные вставки могут подтормаживать; зато на AddRange можно немножко наиграть за счёт повторного использования.
V>Ну вот как раз ради случая хеш-таблицы в хеш-таблицах всё и затевалось. V>Capacity каждой хеш-таблицы относительно невелик (23, 19, 17, 13, 11), пересоздавать при изменении достаточно одну таблицу, а не всю цепочку их до корневого узла, как оно есть в иммутабельных деревьях при балансировке.
Да, мысль хорошая. У меня задачи делать IDictionary<,> не стояло, а для IList<> B-дерево выглядит самой эффективной реализацией.
V>Самый простой вариант — складывать конфликты в упорядоченный список (массив конфликтующих значений).
Да, именно этот вариант первым приведён у Кнута, емнип.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[56]: MS забило на дотнет. Питону - да, сишарпу - нет?
Здравствуйте, vdimas, Вы писали: V>Есть. V>Для методов структур это readonly.
О, прикольно. Про этот — не знал. Интересно, как всё это чудо ведёт себя при вызове внешних методов. И вообще, статья написана максимально мутно. Сначала пишут, что если метод обращается только к readonly мемберам, то защитной копии не создаётся. И тут же идёт абзац про то, что immutable structs всё-таки быстрее. Непонятно, чему верить.
Короче, надо экспериментировать.
V>Для методов классов — это [Pure]
Ну, это вообще из области code contracts, т.е., как я понял, на джит и компилятор не влияет никак. Просто атрибут для просто личного удобства.
V>Там много тащить для "немедленной проверки", но проверю как руки дойдут.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[58]: MS забило на дотнет. Питону - да, сишарпу - нет?
Здравствуйте, Sinclair, Вы писали:
S>Я плохо знаком с миром ФП, и пока что до меня не доходит, каким волшебным образом memoize поможет сделать из мутабельной хеш-таблицы эффективную иммутабельную.
Еще раз. Оно не делает ее иммутабельной, оно делает ее чистой. Мутабельность внутри сохраняется, но на чистоту не влияет, потому что нет видимого изменения состояния.