Что внешняя память что оперативка имеет один и тот же параметр — размер блока и понятие локальности когда работать с размерами меньше этого блока не имеет смысла потому что любое обращение к памяти будет грузить или записывать весь этот блок. Соотвественно B-деревья походят не только для внешней памяти а и для структур в памяти поскольку нет смысла делать узел меньше чем размер блока (для оперативной памяти вместо блока используется название "кеш-линия") — то есть для современных процессоров нет смысла делать узел дерева размером меньше 64 байта (размер кеш-линии в x64 и arm). И узнать какой будет ранг у б-дерева можно просто поделив 64 байта на размер указателя на другие узлы (указатели тоже можно закодировать более компактно если ограничить их общее количество)
Когда падает? В рантайме? В момент рендера нужного компонента? То есть для того чтобы задетектить ошибку непрокинутого стора нужно будет запустить приложение, наклацать нужное состояние чтобы отрендерился нужный компонент и только тогда в консоли увидеть ошибку отсутствующего стора? Нет уж, спасибо, я лучше буду импорты юзать с которыми тайпскрипт сразу в редакторе подсветит ошибку в момент копирования файла с компонентом в проект
А используя inject тайпскрипт или флоу покажет ошибку если не был прокинут стор через Provider? Преимущество импортов в том что они статически тайпчекаются и ошибки будут видны сразу а вот все эти инжекты и провайдеры (и прочие di) под большим сомнением (причем если и тайпчекаются то скорее всего нужно будет заимпортить тип чтобы передать дженерик инжекту и тогда смысл в этом вообще пропадает если все равно связь через импорты)
Там сложность зависит от того насколько сильно изменился массив — K*Log(N) где K это количество перестановок в массиве. Например если в списке из 10к элементов произошла перестановка 3 элементов то LIS будет практически линейным — будет последовательно заполнятся массив из 10к элементов и только в трех случаях будет бинарный поиск до нужного элемента. Зато LIS позволит выполнить всего три insertBefore dom-операции вместо почти полного перестроения всего списка как это делает реакт
Интересно а как вы с веб-компонентами работаете с динамическими списками? Допустим есть список тодошек где можно удалить элемент или вставить новый в любое место в списке. Как вы будете синхронизировать изменения списка тодошек в состоянии с шаблоном? Реакт, ангуляр и вью выполняют так называемый diff списка сравнивая элементы по ключу и перемещая нужные элементы чтобы избежать проблем соответствия компонентов и их дом-элементов. А некоторые фреймворки вроде ivijs или infernojs для сравнения списков вообще реализуют LIS алгоритм который вычисляет минимальное количество перестановок чтобы в итоге уменьшить число dom-операций insertBefore
Не знаю насколько сейчас продвинулись компиляторы в анализе но если человек будет оптимизировать код вручную то с рекурсией он получит аналогичную производительность (если конечно поддерживается базовая оптимизация хвостовой рекурсии)
Например n-ное значение последовательности фибоначчи
function fib(n) {
return n <= 1 ? n : fib(n - 1) + fib(n - 2);
}
человек заоптимизирует через цикл так
function fib (n){
if(n <= 1) return n;
let fib = [0, 1];
for (let i = 2; i <= n; i++) {
fib[i] = fib[i - 2] + fib[i - 1];
}
return fib[fib.length - 1];
}
но с рекурсией можно записать так
function fib (n){
if(n <= 1) return n;
let fib = [0, 1];
let i = 1;
let recur = () => {
i++;
fib[i] = fib[i - 2] + fib[i - 1];
if(i < n) recur();
};
recur();
return fib[fib.length - 1];
}
И любой компилятор который умеет в хвостовую рекурсию будет выполнять этот код так же быстро как и с циклом.
Что касается удобства — да, получается на пару строчек больше но сейчас и так мало где можно встретить ручные циклы (обычно всякие .map(), .filter и т.д) так что экономия пары строчек для кода который будет занимать меньше 1 процента от всего проекта не имеет смысла. Зато мы избавились от кучи лишних синтаксический конструкций в языке (for, while, break, continue и labels) и упростили не только язык но и работу статическому анализатору (так как ему теперь нужно будет заняться анализом только рекурсии а не учитывать еще кучу кейсов и контрукций обычных циклов)
Ну и напоследок самое вкусное. Тут, к сожалению, не у всех хватает опыта понять нюанс. Я против использования рекурсивных функций. Да, они сокращают объем написанного кода, но, не обходится без подвоха: часто такие рекурсивные функции не имеют условий выхода, про них просто-напросто забывают.
Если у рекурсии не будет условий выхода то она будет выполняться бесконечно. И статический анализатор мог бы проверить что условий выхода нет и подсветить ошибку в редакторе.
Вообще у меня есть мнение что для высокоуровневого языка программирования надо запрещать как раз таки циклы и оставить рекурсию как единственное средство для итераций. Суть такого решения в том что не нужно для языка программирования иметь несколько способов сделать одно то же — раз мы одну и ту же задачу можем решить и с помощью рекурсии и с помощью циклов то очевидно что одно их этих средств является избыточным. И появляется вопрос что убрать — циклы или рекурсию. Очевидно что рекурсия это более высокий уровень абстракции (рекурсия сводится к циклам через стек) — а значит более мощный и удобный инструмент. А что касается производительности то это уже задача компилятора (или статического анализатора с транспиляцией) преобразовать tail-call рекурсию в цикл без стека или провести кучу других оптимизаций. Что же касается циклов то мало того что в том же js для них есть разные избыточные синтаксические конструкции (while, do-while, for) да еще и с кучей нюансов и вопросов на который не каждый js-миддл ответит правильно (https://www.youtube.com/watch?v=Nzokr6Boeaw)
Логически квадрат это не частный случай прямоугольника а вычисляемое свойство, то есть сущность прямоугольника которая позволяет менять ширину и длину время от времени может становится квадратом при совпадении длины и ширины
В статье был упомянут MirageOS, но почему-то не упоминается проект IncludeOS который является более известным и мейнстримовым — с++ а не ocaml (советую посмотреть на ютюбе несколько докладов про includeos где также освещается общая тема unikernels, например в вопросе безопасности — https://youtu.be/t4etEwG2_LY?t=1810)
Не совсем ясно как эта база данных решает известную проблему денормализации — происходит трек зависимостей и полное перевычисление или анализируются выражения для инкрементальных изменений? Решена ли проблема ромбовидных зависимостей (когда вычисление какой-то вьюхи может запуститься дважды)? Что насчет возможности получения реалтайм-оповещений об изменениях материализованной вьюхи? Что насчет возможности инкрементальной синхронизации (когда при восстановлении оборванного соединения через которого клиент получал оповещения от материализованной вьюхи будет происходить загрузка не всех данных вьюхи а только тех которые изменились за время оффлайна) ?
Если взглянуть поглубже то окажется что современная джава или нода практически одинаковы по критериям скорости обработки сетевых запросов и скорости вычислений. Но об этом по порядку:
1) Скорость обработки сетевых запросов
В Java мы можем создать приложение и запустить в нем 8 потоков. За счет того, что происходит более тесное взаимодействие с ОС, можно распределить нагрузку.
и в ноде тоже можно точно так же только называются такие потоки воркерами
А когда приходит запрос на node, цикл событий (event loop) будет обработан и отправлен обратно, затем придет следующий запрос. И за счет того, что мы не ждем результатов первого, он тоже будет подхвачен.
и в java можно делать то же самое (nio, nio2 и т.д)
Многие не понимают что модель работы с сетевыми (и другими io-) запросами зависит не от языка и даже не от движка а от того какой апи операционной системы проброшен в язык — можно обрабатывать сетевые запросы создавая поток на каждый запрос а можно через мультиплексирующие или асинхронные апи ос (например poll/select/epoll/signals в линуксе) — и на этом сравнение становится совершенно бессмысленным потому что в джаве что в ноде можно пробросить любой апи операционной системы и соотвественно эффективность обработки запросов тут целиком зависит от ос и никак не от языка или движка (если конечно не накосячить с пробросом). А то что в ноде принято запросы обрабатывать через цикл событий а в джаве через потоки (а например в php еще можно через процессы) то это просто потому что джава появилась раньше а раньше люди были глупее и так сложилось исторически и продолжает еще двигаться по инерции. Единственная техническая разница движка которая может повлиять на проектирование и производительность заключается в том что в движках джавы обращение к общей памяти чужого потока а также общение между потоками (атомарные операции, мютексы, спин-локи и т.д) доступно на уровне объектов (с кучей сахара) а в джаваскриптовский движок v8 лимитирован пока только отдельными запросами чтения-записи на уровне массива байт через SharedArrayBuffer (но с другой стороны ничто вам не мешает построить вокруг этого массива байт свою абстракцию объектов и структур данных)
2) Скорость вычислений. Тут говорить что мол джава хороша для вычислений, сложных алгоритмов или бизнес-логики, а нода менее хороша (или наоборот) — бессмысленно и это очевидно каждому кто понимает как работает jit и процессоры. Современные движки что в js (в частности v8 который стоит в ноде) что в java уже довольно давно стали компилировать код в машинные инструкции с различными компиляторными оптимизациями поэтому разница по производительности будет минимальной. Понятно что может быть так что у одного движка может не реализована какая-то одна оптимизация а у другого другая но если не писать микробенчмарков направленных на выявления конкретных оптимизаций то можно сказать что общий уровень производительности обоих языков и движков примерно одинаков. А тот факт что джава статически типизируемый язык а жс нет — хотя и может сэкономить движку джавы одну ассемблерную инструкцию (которая требуется v8 для спекулятивной проверки рантайм-типа) но практическую разницу в скорости вы даже не увидите так как в реальных приложениях практически всегда всплывут сначала проблемы неоптимальной локальности данных (для сравнения один кеш-промах или инструкция загрузки из памяти а не из кеша равен нескольким сотням других ассеблерных инструкций, так что разницу в одну ассеблерную инструкцию вы даже не увидите)
У mobx самый главный недостаток в том что автор добавил в стейт-менеджер кучу опциональных вещей а народ записывает опциональные вещи в недостатки. Если вам не нравятся декораторы, прокси, геттеры-сеттеры, или кастомные структуры которыми мобикс оборачивает массив, мапы и сеты — то никто не заставляет вас их использовать — можно использовать только апи атомов в мобиксе и получить стейт-менеджер с таким же по удобству апи как и у того же effector-а
Из статьи неясно зачем нужно выделять токенизацию или лексический анализ отдельно от основного парсера. Почему бы не интегрировать токенизацию прямо в рекурсивный спуск парсера — получим один проход вместо двух и заодно возможность контекстно-зависимых ключевых слов
придется считать линию в кеш (предварительно вытеснив что-то из него), обновить её и записать обратно. Т.е. как минимум какие-то данные из кеша будут вытеснены почем зря.
А разве нет инструкции для записи в память минуя кеш не вытесняя при этом из него другие данные? Насколько я помню для чтения есть инструкции (non-temporal) которые позволяют считать из памяти в регистры не кешируя (на случай если мы хотим один раз вычитать что-то не вытеснив при этом более важные оперативные данные из кеша). Есть ли что-то подобное для записи?
К описаным вами сомнительным преимуществам декларативности когда в шаблоне не может быть js-выражений добавляется существенный минус — необходимость именования — если раньше можно было указать нужное выражение прямо в шаблоне то теперь придется придумать имя и по этому имени вынести в другое место нужное выражение. А необходимость именования создает точку ручной синхронизации — каждый раз меняя это имя нужно не забыть переименовать в шаблоне (или наоборот). А если потом было решено убрать кусок шаблона то нужно не забыть убрать в другом месте лишние именованные выражения которые были вынесены из шаблона.
Именование хорошо когда оно убирает логическое дублирование кода (когда изменение потребует ручной синхронизации кода в разных местах). В остальных случаях лишнее именование чего либо не было приводит только к усложнению. Например необходимость именовать экшены в редаксе против возможности изменить состояния по месту как mobx-е, или необходимость именовать классы и выносить стили в отдельные местах против возможности указать нужные стили инлайном рядом с тегом или необходимость именовать отдельные роуты в rest-e против возможности вызвать функцию как в rpc. Все это добавляет лишнюю косвенность которая увеличивает количество мест изменения кода в git diff-e а это значит что увеличивается количество неявных связей (программисту нужно держать в голове все места которые требуют изменений при добавлении или изменении/удалении такой-то фичи)
Предположу что mspain под неправильной технологией stateless REST подразумевал http протокол на котором задача друх генералов «решается» сложнее. С вебсокетами или tcp если серверу нужно узнать дошел ли его ответ клиенту то он просто отправит запрос клиенту по тому же соединению и (а tcp будет ретраить отправку) и после получения подтверждения поймет что его ответ дошел (так как сообщения строго упорядочены). А с http упорядоченность запросов не гарантируется — запросы на сервер могут придти не в том порядке в котором были отправлены клиентом (то есть могут пойти по разным tcp-соединениям), ну и также в случае с браузером там канал только в одну сторону (от клиента к серверу) и сервер просто не может самостоятельно отправить запрос клиенту по http
А что делать, если нужно что-то обновить зная только его id, возможно составной? Например от websoket'а прилетело сообщение вида «обновили поле fieldName, в записи rowId, таблицы recordType».
Я не упомянул этот момент — когда нужно загрузить данные или получать оповещения от сервера то действительно нужен еще хеш где нужный объект можно вытащить зная его айдишник. Но это не значит что мы должны везде оперировать айдишниками — со всеми объектами внутри приложения по-прежнему можно обращаться через ссылки просто для нужд синхронизации данных с сервером на объект также будет вести еще одна ссылка из хеш-мапы по его айдишнику (и удобней будет вынести добавление ссылки на объект по его айдишнику в конструктор базового класса)
И тогда получив оповещение по веб-сокетам просто вытаскиваем по айдишнику нужный объект и обновляем нужное поле. А поскольку все объекты в состоянии связаны по ссылкам и распределяются по компонентам от рутового компонент до самых вложенных через передачу ссылок на нужные объекты (как я привел в примере выше) то неважно во скольких местах одновременно будет отображаться данные этого объекта (форма, таблица, модалка и т.д) — при вызове ReactDOM.render(<App/>, rootEl) или используя mobx (который выполнит обновление только нужных компонентов) произойдет актуализация интерфейса в соотвествии с изменениями
Нужно проверять все места в сторе где эта запись может быть? Например
строка в табличной форме
строка при расчетах в графиках дашборда
форма на просмотр одной записи
строка в связанной таблице формы на просмотр одной записи
модалка с формой на редактирование одной записи
строка в связанной таблице модалки с формой на редактирование одной записи
пользователь скрыл эту форму и обновлять ничего не нужно
Ладно попроще пример, есть два списка на одном экране в них одна из строк пересекается, как понять, что при изменении в одном списке нужно обновлять эту строку в другом списке?
Определением какие компоненты нужно обновить при изменении одного/нескольких объектов/полей как раз и является главной задачей стейт-менеджеров и есть несколько вариантов их решения. Сам реакт также решает эту проблему — вызвав ReactDOM.render() на рутовом компоненте реакт проверит все биндиги всего приложения и применит нужные изменения к дом-элементам (и поскольку один и тот же объект находится через ссылки одновременно в двух разных списках то после его изменения ничего дополнительно делать не нужно — реакт сам увидит что изменились биндинги у одного и второго компонента списка).
Но поскольку на больших приложениях такое решение не очень эффективно (а также еще тот факт что реакт использует не самую быструю технику диффа и сам по себе довольно медленный) и будет тормозить на большом количестве компонентов то поэтому и появился весь этот хайп иммутабельности в реакте — мол давайте будем при изменении возвращать новый объект вместе с его родительскими чтобы можно было переопределить shouldComponentUpdate и тем самым реакт сможет заскипать лишние поддеревья интерфейса при диффе. Но такой подход перестает работать на сложных интерфейсах когда связанные сущности будут передаваться через айдишники (а это необходимо потому что данные могут находиться в разных местах интерфейса и хранить копии объекта во всех этих местах и синхронизировать изменения еще сложнее)
Потом появился redux который выполняет проход по всем подключенным компонентам и это позволяет в mapStateToProps вернуть объект по его айдишнику а редакс сравнит старую и новую версию через === и в случае несоответствия обновит сам компонент. А поскольку в обоих списках будет находиться один и тот же айдишник то когда произошло обновление состояния redux увидит что только два компонента вернули новый объект и обновит только два компонента.
А вот mobx работает по другому — каждое поле объекта которое используется в шаблоне оборачивается в некий обзервабл — объект который помимо значения хранит еще список подписчиков на этот объект. И когда начинается процесс рендера враппер над компонентом устанавливает в глобальную переменную текущий инстанс компонента чтобы каждое поле-обзервабл к которому происходит обращение внутри шаблона смогло добавить к себе в подписчики текущий компонент из этой глобальной переменной чтобы при изменении своего значения вызвать обновление всех компонентов-подписчиков на это поле. Ну а благодаря js-геттерам и сеттерам обращение к таким полям-обзерваблам и изменения их значения синтаксически ничем не отличается от работы с обычными js-объектами, а отсутствие необходимости в иммутабельности и возможность связывать объекты по ссылкам сводит к минимуму количество неудобств связанных с внедрением стейт-менеджера в проект в сравнении с версией когда используются обычные js-объекты и дифф реакта
Странно ожидать от инструмента вещей, которых он не умеет. То есть вы изначально на компонент системы который служит некой оберткой для вашего кода, возлагаете надежды как на постгрес.
так и редакс не запрещает. поймите, вы от носка хотите, чтобы он был ракетой.
Это не редакс не умеет, это он как раз таки и запрещает обычным js-объектам ссылаться друг на друга когда не используются никакие стейт-менеджеры. Почему запрещает? Потому что иммутабельный подход редакса говорит о том что нужно вернуть новый объект состояния. А если изменения произошли где-то глубоко в этом объекте то нужно также вернуть новый объект всех его предков. А теперь что будет если объекты будут ссылаться друг на друга? В простом примере когда юзер может создавать проекты и в проектах задачи и если каждая задача будет вместо айдишника (task.projectId) хранить ссылку на сам объект проекта то уже недостаточно просто вернуть новые родительские объекты — как только происходит создание нового объекта проекта то все его задачи станут ссылаться на старый объект а это значит что нужно будет пройтись по задачам и обновить у них ссылку на новый объект проекта. Но просто обновить ссылку вы тоже не можете — mapStateToProps не увидит изменения и не перерисует компонент, поэтому нужно пересоздать новый объект каждой задачи проекта. Тоже самое с юзером и проектами. В итоге получится что как только объекты будут ссылаться на родительские то обновление одного объекта приведет к рекурсивному пересозданию всех объектов всего состояния. И если в простых приложениях ссылаться на родителя требуется нечасто и можно обойтись айдишником то в сложных приложениях где одни и же же данные могут отображаться в разных местах, а также в случае many-to-many связей проблема становится все актуальней и проще хранить все состояние в нормализированном виде и всегда ссылаться по айдишникам.
По большому счету все стейт-менеджеры решают одну и ту же проблему — проблему эффективного обновления компонентов. Иначе можно было бы просто хранить состояние в виде глобального js-объекта а в нем хранить любую вложенность объектов с ссылками друг на друга,
И ссылки на отдельные объекты этого состояния можно удобно передавать компонентам (без всяких джойнов как с нормализованным состоянием в редаксе) и также удобно обращаться по обратным ссылкам ссылкам (обратите внимание на строчку <div>in folder: {task.project.folder.name}</div>)
Таким образом можно удобно работать с состоянием и стейт-менеджер был бы не нужен но вот беда — реакт не умеет быстро выполнять diff всего приложения и на больших spa-приложениях это будет тормозить. И тут на сцену приходят стейт-менеджеры которые как раз и решают проблему тормозов реакта — они соотносят какие компоненты от каких объектов или даже полей зависят чтобы выполнить обновления только нужных компонентов когда объекты изменятся.
И получается что вполне логично оценивать стейт-менеджеры по тому каких удобств они лишают в сравнении с разработкой без стейт-менеджера используя обычными js-объекты. Редакс решает проблему точечных обновлений за счет иммутабельного подхода что лишает всех удобств работы с ссылками (вдобавок к неудобствам писать деструктуризации и собирать новые объекты в редюсерах). А например mobx не требует иммутабельности и позволяет объектам ссылаться друг на друга а проблему точечных обновлений решает по-другому (и версия с mobx за исключением добавления декораторов к полям на классах объектов ничем не будет отличаться от примера выше с обычными js-объектами)
B-деревья эффективнее работают с кешем процессора
Что внешняя память что оперативка имеет один и тот же параметр — размер блока и понятие локальности когда работать с размерами меньше этого блока не имеет смысла потому что любое обращение к памяти будет грузить или записывать весь этот блок. Соотвественно B-деревья походят не только для внешней памяти а и для структур в памяти поскольку нет смысла делать узел меньше чем размер блока (для оперативной памяти вместо блока используется название "кеш-линия") — то есть для современных процессоров нет смысла делать узел дерева размером меньше 64 байта (размер кеш-линии в x64 и arm). И узнать какой будет ранг у б-дерева можно просто поделив 64 байта на размер указателя на другие узлы (указатели тоже можно закодировать более компактно если ограничить их общее количество)
Когда падает? В рантайме? В момент рендера нужного компонента? То есть для того чтобы задетектить ошибку непрокинутого стора нужно будет запустить приложение, наклацать нужное состояние чтобы отрендерился нужный компонент и только тогда в консоли увидеть ошибку отсутствующего стора? Нет уж, спасибо, я лучше буду импорты юзать с которыми тайпскрипт сразу в редакторе подсветит ошибку в момент копирования файла с компонентом в проект
А используя inject тайпскрипт или флоу покажет ошибку если не был прокинут стор через Provider? Преимущество импортов в том что они статически тайпчекаются и ошибки будут видны сразу а вот все эти инжекты и провайдеры (и прочие di) под большим сомнением (причем если и тайпчекаются то скорее всего нужно будет заимпортить тип чтобы передать дженерик инжекту и тогда смысл в этом вообще пропадает если все равно связь через импорты)
Там сложность зависит от того насколько сильно изменился массив — K*Log(N) где K это количество перестановок в массиве. Например если в списке из 10к элементов произошла перестановка 3 элементов то LIS будет практически линейным — будет последовательно заполнятся массив из 10к элементов и только в трех случаях будет бинарный поиск до нужного элемента. Зато LIS позволит выполнить всего три insertBefore dom-операции вместо почти полного перестроения всего списка как это делает реакт
Про diff списка на основе LIS неплохо описано
в самих исходниках ivi — https://github.com/localvoid/ivi/blob/master/packages/ivi/src/vdom/reconciler.ts#L578. А сам алгоритм LIS неплохо объясняется в этом видео
https://www.youtube.com/watch?v=1RpMc3fv0y4
Интересно а как вы с веб-компонентами работаете с динамическими списками? Допустим есть список тодошек где можно удалить элемент или вставить новый в любое место в списке. Как вы будете синхронизировать изменения списка тодошек в состоянии с шаблоном? Реакт, ангуляр и вью выполняют так называемый diff списка сравнивая элементы по ключу и перемещая нужные элементы чтобы избежать проблем соответствия компонентов и их дом-элементов. А некоторые фреймворки вроде ivijs или infernojs для сравнения списков вообще реализуют LIS алгоритм который вычисляет минимальное количество перестановок чтобы в итоге уменьшить число dom-операций insertBefore
Не знаю насколько сейчас продвинулись компиляторы в анализе но если человек будет оптимизировать код вручную то с рекурсией он получит аналогичную производительность (если конечно поддерживается базовая оптимизация хвостовой рекурсии)
Например n-ное значение последовательности фибоначчи
человек заоптимизирует через цикл так
но с рекурсией можно записать так
И любой компилятор который умеет в хвостовую рекурсию будет выполнять этот код так же быстро как и с циклом.
Что касается удобства — да, получается на пару строчек больше но сейчас и так мало где можно встретить ручные циклы (обычно всякие .map(), .filter и т.д) так что экономия пары строчек для кода который будет занимать меньше 1 процента от всего проекта не имеет смысла. Зато мы избавились от кучи лишних синтаксический конструкций в языке (for, while, break, continue и labels) и упростили не только язык но и работу статическому анализатору (так как ему теперь нужно будет заняться анализом только рекурсии а не учитывать еще кучу кейсов и контрукций обычных циклов)
Если у рекурсии не будет условий выхода то она будет выполняться бесконечно. И статический анализатор мог бы проверить что условий выхода нет и подсветить ошибку в редакторе.
Вообще у меня есть мнение что для высокоуровневого языка программирования надо запрещать как раз таки циклы и оставить рекурсию как единственное средство для итераций. Суть такого решения в том что не нужно для языка программирования иметь несколько способов сделать одно то же — раз мы одну и ту же задачу можем решить и с помощью рекурсии и с помощью циклов то очевидно что одно их этих средств является избыточным. И появляется вопрос что убрать — циклы или рекурсию. Очевидно что рекурсия это более высокий уровень абстракции (рекурсия сводится к циклам через стек) — а значит более мощный и удобный инструмент. А что касается производительности то это уже задача компилятора (или статического анализатора с транспиляцией) преобразовать tail-call рекурсию в цикл без стека или провести кучу других оптимизаций. Что же касается циклов то мало того что в том же js для них есть разные избыточные синтаксические конструкции (while, do-while, for) да еще и с кучей нюансов и вопросов на который не каждый js-миддл ответит правильно (https://www.youtube.com/watch?v=Nzokr6Boeaw)
Логически квадрат это не частный случай прямоугольника а вычисляемое свойство, то есть сущность прямоугольника которая позволяет менять ширину и длину время от времени может становится квадратом при совпадении длины и ширины
В статье был упомянут MirageOS, но почему-то не упоминается проект IncludeOS который является более известным и мейнстримовым — с++ а не ocaml (советую посмотреть на ютюбе несколько докладов про includeos где также освещается общая тема unikernels, например в вопросе безопасности — https://youtu.be/t4etEwG2_LY?t=1810)
Не совсем ясно как эта база данных решает известную проблему денормализации — происходит трек зависимостей и полное перевычисление или анализируются выражения для инкрементальных изменений? Решена ли проблема ромбовидных зависимостей (когда вычисление какой-то вьюхи может запуститься дважды)? Что насчет возможности получения реалтайм-оповещений об изменениях материализованной вьюхи? Что насчет возможности инкрементальной синхронизации (когда при восстановлении оборванного соединения через которого клиент получал оповещения от материализованной вьюхи будет происходить загрузка не всех данных вьюхи а только тех которые изменились за время оффлайна) ?
1) Скорость обработки сетевых запросов
и в ноде тоже можно точно так же только называются такие потоки воркерами
и в java можно делать то же самое (nio, nio2 и т.д)
Многие не понимают что модель работы с сетевыми (и другими io-) запросами зависит не от языка и даже не от движка а от того какой апи операционной системы проброшен в язык — можно обрабатывать сетевые запросы создавая поток на каждый запрос а можно через мультиплексирующие или асинхронные апи ос (например poll/select/epoll/signals в линуксе) — и на этом сравнение становится совершенно бессмысленным потому что в джаве что в ноде можно пробросить любой апи операционной системы и соотвественно эффективность обработки запросов тут целиком зависит от ос и никак не от языка или движка (если конечно не накосячить с пробросом). А то что в ноде принято запросы обрабатывать через цикл событий а в джаве через потоки (а например в php еще можно через процессы) то это просто потому что джава появилась раньше
а раньше люди были глупееи так сложилось исторически и продолжает еще двигаться по инерции. Единственная техническая разница движка которая может повлиять на проектирование и производительность заключается в том что в движках джавы обращение к общей памяти чужого потока а также общение между потоками (атомарные операции, мютексы, спин-локи и т.д) доступно на уровне объектов (с кучей сахара) а в джаваскриптовский движок v8 лимитирован пока только отдельными запросами чтения-записи на уровне массива байт через SharedArrayBuffer (но с другой стороны ничто вам не мешает построить вокруг этого массива байт свою абстракцию объектов и структур данных)2) Скорость вычислений. Тут говорить что мол джава хороша для вычислений, сложных алгоритмов или бизнес-логики, а нода менее хороша (или наоборот) — бессмысленно и это очевидно каждому кто понимает как работает jit и процессоры. Современные движки что в js (в частности v8 который стоит в ноде) что в java уже довольно давно стали компилировать код в машинные инструкции с различными компиляторными оптимизациями поэтому разница по производительности будет минимальной. Понятно что может быть так что у одного движка может не реализована какая-то одна оптимизация а у другого другая но если не писать микробенчмарков направленных на выявления конкретных оптимизаций то можно сказать что общий уровень производительности обоих языков и движков примерно одинаков. А тот факт что джава статически типизируемый язык а жс нет — хотя и может сэкономить движку джавы одну ассемблерную инструкцию (которая требуется v8 для спекулятивной проверки рантайм-типа) но практическую разницу в скорости вы даже не увидите так как в реальных приложениях практически всегда всплывут сначала проблемы неоптимальной локальности данных (для сравнения один кеш-промах или инструкция загрузки из памяти а не из кеша равен нескольким сотням других ассеблерных инструкций, так что разницу в одну ассеблерную инструкцию вы даже не увидите)
А разве нет инструкции для записи в память минуя кеш не вытесняя при этом из него другие данные? Насколько я помню для чтения есть инструкции (non-temporal) которые позволяют считать из памяти в регистры не кешируя (на случай если мы хотим один раз вычитать что-то не вытеснив при этом более важные оперативные данные из кеша). Есть ли что-то подобное для записи?
Именование хорошо когда оно убирает логическое дублирование кода (когда изменение потребует ручной синхронизации кода в разных местах). В остальных случаях лишнее именование чего либо не было приводит только к усложнению. Например необходимость именовать экшены в редаксе против возможности изменить состояния по месту как mobx-е, или необходимость именовать классы и выносить стили в отдельные местах против возможности указать нужные стили инлайном рядом с тегом или необходимость именовать отдельные роуты в rest-e против возможности вызвать функцию как в rpc. Все это добавляет лишнюю косвенность которая увеличивает количество мест изменения кода в git diff-e а это значит что увеличивается количество неявных связей (программисту нужно держать в голове все места которые требуют изменений при добавлении или изменении/удалении такой-то фичи)
Я не упомянул этот момент — когда нужно загрузить данные или получать оповещения от сервера то действительно нужен еще хеш где нужный объект можно вытащить зная его айдишник. Но это не значит что мы должны везде оперировать айдишниками — со всеми объектами внутри приложения по-прежнему можно обращаться через ссылки просто для нужд синхронизации данных с сервером на объект также будет вести еще одна ссылка из хеш-мапы по его айдишнику (и удобней будет вынести добавление ссылки на объект по его айдишнику в конструктор базового класса)
И тогда получив оповещение по веб-сокетам просто вытаскиваем по айдишнику нужный объект и обновляем нужное поле. А поскольку все объекты в состоянии связаны по ссылкам и распределяются по компонентам от рутового компонент до самых вложенных через передачу ссылок на нужные объекты (как я привел в примере выше) то неважно во скольких местах одновременно будет отображаться данные этого объекта (форма, таблица, модалка и т.д) — при вызове ReactDOM.render(<App/>, rootEl) или используя mobx (который выполнит обновление только нужных компонентов) произойдет актуализация интерфейса в соотвествии с изменениями
Определением какие компоненты нужно обновить при изменении одного/нескольких объектов/полей как раз и является главной задачей стейт-менеджеров и есть несколько вариантов их решения. Сам реакт также решает эту проблему — вызвав ReactDOM.render() на рутовом компоненте реакт проверит все биндиги всего приложения и применит нужные изменения к дом-элементам (и поскольку один и тот же объект находится через ссылки одновременно в двух разных списках то после его изменения ничего дополнительно делать не нужно — реакт сам увидит что изменились биндинги у одного и второго компонента списка).
Но поскольку на больших приложениях такое решение не очень эффективно (а также еще тот факт что реакт использует не самую быструю технику диффа и сам по себе довольно медленный) и будет тормозить на большом количестве компонентов то поэтому и появился весь этот хайп иммутабельности в реакте — мол давайте будем при изменении возвращать новый объект вместе с его родительскими чтобы можно было переопределить shouldComponentUpdate и тем самым реакт сможет заскипать лишние поддеревья интерфейса при диффе. Но такой подход перестает работать на сложных интерфейсах когда связанные сущности будут передаваться через айдишники (а это необходимо потому что данные могут находиться в разных местах интерфейса и хранить копии объекта во всех этих местах и синхронизировать изменения еще сложнее)
Потом появился redux который выполняет проход по всем подключенным компонентам и это позволяет в mapStateToProps вернуть объект по его айдишнику а редакс сравнит старую и новую версию через === и в случае несоответствия обновит сам компонент. А поскольку в обоих списках будет находиться один и тот же айдишник то когда произошло обновление состояния redux увидит что только два компонента вернули новый объект и обновит только два компонента.
А вот mobx работает по другому — каждое поле объекта которое используется в шаблоне оборачивается в некий обзервабл — объект который помимо значения хранит еще список подписчиков на этот объект. И когда начинается процесс рендера враппер над компонентом устанавливает в глобальную переменную текущий инстанс компонента чтобы каждое поле-обзервабл к которому происходит обращение внутри шаблона смогло добавить к себе в подписчики текущий компонент из этой глобальной переменной чтобы при изменении своего значения вызвать обновление всех компонентов-подписчиков на это поле. Ну а благодаря js-геттерам и сеттерам обращение к таким полям-обзерваблам и изменения их значения синтаксически ничем не отличается от работы с обычными js-объектами, а отсутствие необходимости в иммутабельности и возможность связывать объекты по ссылкам сводит к минимуму количество неудобств связанных с внедрением стейт-менеджера в проект в сравнении с версией когда используются обычные js-объекты и дифф реакта
Это не редакс не умеет, это он как раз таки и запрещает обычным js-объектам ссылаться друг на друга когда не используются никакие стейт-менеджеры. Почему запрещает? Потому что иммутабельный подход редакса говорит о том что нужно вернуть новый объект состояния. А если изменения произошли где-то глубоко в этом объекте то нужно также вернуть новый объект всех его предков. А теперь что будет если объекты будут ссылаться друг на друга? В простом примере когда юзер может создавать проекты и в проектах задачи и если каждая задача будет вместо айдишника (task.projectId) хранить ссылку на сам объект проекта то уже недостаточно просто вернуть новые родительские объекты — как только происходит создание нового объекта проекта то все его задачи станут ссылаться на старый объект а это значит что нужно будет пройтись по задачам и обновить у них ссылку на новый объект проекта. Но просто обновить ссылку вы тоже не можете — mapStateToProps не увидит изменения и не перерисует компонент, поэтому нужно пересоздать новый объект каждой задачи проекта. Тоже самое с юзером и проектами. В итоге получится что как только объекты будут ссылаться на родительские то обновление одного объекта приведет к рекурсивному пересозданию всех объектов всего состояния. И если в простых приложениях ссылаться на родителя требуется нечасто и можно обойтись айдишником то в сложных приложениях где одни и же же данные могут отображаться в разных местах, а также в случае many-to-many связей проблема становится все актуальней и проще хранить все состояние в нормализированном виде и всегда ссылаться по айдишникам.
По большому счету все стейт-менеджеры решают одну и ту же проблему — проблему эффективного обновления компонентов. Иначе можно было бы просто хранить состояние в виде глобального js-объекта а в нем хранить любую вложенность объектов с ссылками друг на друга,
И ссылки на отдельные объекты этого состояния можно удобно передавать компонентам (без всяких джойнов как с нормализованным состоянием в редаксе) и также удобно обращаться по обратным ссылкам ссылкам (обратите внимание на строчку
<div>in folder: {task.project.folder.name}</div>
)а для изменения объекта достаточно просто изменить нужное поле и вызвать функцию rerender() которая вызовет перерендер реакта (diff) всего приложения
Таким образом можно удобно работать с состоянием и стейт-менеджер был бы не нужен но вот беда — реакт не умеет быстро выполнять diff всего приложения и на больших spa-приложениях это будет тормозить. И тут на сцену приходят стейт-менеджеры которые как раз и решают проблему тормозов реакта — они соотносят какие компоненты от каких объектов или даже полей зависят чтобы выполнить обновления только нужных компонентов когда объекты изменятся.
И получается что вполне логично оценивать стейт-менеджеры по тому каких удобств они лишают в сравнении с разработкой без стейт-менеджера используя обычными js-объекты. Редакс решает проблему точечных обновлений за счет иммутабельного подхода что лишает всех удобств работы с ссылками (вдобавок к неудобствам писать деструктуризации и собирать новые объекты в редюсерах). А например mobx не требует иммутабельности и позволяет объектам ссылаться друг на друга а проблему точечных обновлений решает по-другому (и версия с mobx за исключением добавления декораторов к полям на классах объектов ничем не будет отличаться от примера выше с обычными js-объектами)