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


54

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

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


Забыл упомянуть: я специально спрашиваю о C и C ++, в отличие от таких языков, как Python, где есть явное преимущество (скорость выполнения) использования библиотеки.


14
С одной стороны, вы не захотите изобретать велосипед. С другой стороны, лучший способ понять алгоритм (и, как следствие, легко диагностировать случаи, когда алгоритм неожиданно терпит неудачу) - это попытаться написать код реализации самостоятельно.
JM

2
Вы осуждаете каждую теорему, с которой сталкиваетесь? Может быть, вы попробуете это и поиграете с какими-то детскими чемоданами, но если это не сфокусировано на ваших исследованиях, вы, вероятно, примете это и продолжите жить.
DLS

3
Физики не программисты, и они не привыкли иметь дело с кодом других людей (читая или исправляя его). Когда им действительно приходится использовать чужой код, он часто не очень хорошо написан или хорошо прокомментирован, написан другими физиками, что снова добавляет к идее, что лучше переписать его, чем повторно использовать. Это верно, по крайней мере, в некоторых областях / сообществах, но отношение меняется среди молодых людей. Однако не все так плохо, подумайте об отношении плохого студента CS, который не может что-то сделать, если он не может найти достаточно легкую библиотеку для этого.
Сабольч

Ответы:


45

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

  • Код, который был протестирован сотнями / тысячами или более пользователями
  • Код, который будет обновляться и улучшаться в будущем, без какой-либо работы с вашей стороны
  • Оптимизированный код, более эффективный и, возможно, более масштабируемый, чем тот, который вы написали бы с первой попытки.
  • В зависимости от библиотеки, установив интерфейс с ней в своем коде, вы можете получить доступ ко многим алгоритмам, которые вы в настоящее время не используете, но, возможно, захотите в будущем

В последнем пункте выше я имею в виду большие библиотеки, такие как Trilinos или PETSc . Я могу подкрепить это несколькими конкретными личными примерами разработки PyClaw . Хотя было бы просто распараллелить Clawpack с вызовами MPI, мы решили использовать PETSc. Это позволило нам ограничить параллельный код в пакете менее чем 300 строками Python, но еще лучше, поместив наши данные в формат PETSc, мы получили немедленный доступ к неявным решателям PETSc, что позволяет в настоящее время работать с неявным решателем в PyClaw. В качестве второго примера, PyClaw изначально включал код WENO реконструкции пятого порядка, но в итоге мы решили положиться на PyWENOпакет для этого. Это было огромным преимуществом, поскольку PyWENO может автоматически генерировать подпрограммы WENO любого порядка на нескольких языках.

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


5
«Вы можете внести свой вклад, разработав улучшения или найдя ошибки, которые пойдут на пользу многим другим людям». - это удовлетворило бы и желание "возиться / учиться", и лень (не нужно делать то, что уже было сделано). :)
JM

1
Смотрите также, крайние случаи. Для многих алгоритмов тривиально реализовать что-то, что «работает», но не будет правильно обрабатывать небольшую часть случаев. Это может быть хорошо для небольшого проекта, но я не могу сосчитать, сколько раз меня зацепили патологические состояния чего-то, что я «оптимизировал» сам.
meawoppl

34

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

Python преуспел в сокращении этих издержек с помощью таких инструментов, как pip / easy_install и унифицированного интерфейса структуры данных (то есть каждая библиотека, кажется, принимает и генерирует массивный массив).


19

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

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

А теперь представьте, что вам нужен инструмент, которого нет в этой цепочке инструментов (это случилось со мной только в прошлом месяце). У вас есть три варианта

  1. Брось себе. Со всеми рисками и неприятностями, которые связаны.
  2. Вычеркните какой-нибудь код из библиотеки и включите его в Project. Это означает, что вы должны взять на себя обслуживание в будущем, и что вам придется понимать чужой код, когда это произойдет.
  3. Пойдите к людям, которые поддерживают цепочку инструментов, попросите их о том, что вам нужно, и затем ждите цикла выпуска, чтобы получить это. Эти парни довольно отзывчивы, но вы должны сделать это без рабочего кода или после того, как вы сделали (1) или (2).

Это очень легко выбрать (1). Может быть, слишком просто.


Да, добавленные зависимости являются существенным недостатком использования библиотек.
Дэвид Кетчон

Зависимости - большой недостаток в моей голове
Fomite

2
Вполне возможно, что в моем ответе слишком много внимания уделяется факту зависимостей, а недостаточно - бюрократическому процессу получения утвержденного по зависимостям объявления в больших проектах.
dmckee

* Ваш пункт 3. (Извините за придирку.)
299792458

Э ... нет. Это говорит о том, что я имею в виду.
dmckee

12

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

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

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

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


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

10

Я думаю, что реализация алгоритма вместо использования библиотеки может иногда дать лучшее понимание и контроль модели. Когда я пишу какую-то программу для научных вычислений, мне важно понять, что я делаю. Реализация важных алгоритмов помогает мне лучше понять проблему и лучше ее контролировать.

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

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


1
+1 за улучшение понимания. Хотя это больше проблема для ваших собственных алгоритмов, чем для рутины библиотеки.
Фахим Митха

8

Один из ответов заключается в том, что в числовом коде так много незначительных вариаций, что его трудно заключить в библиотеку. Возьмите это по сравнению с веб-программным обеспечением, которое часто легко установить и имеет четкий набор входов и выходов. Я думаю, что более распространенными являются люди, которые берут фреймворк или большую библиотеку, которая действует как фреймворк (Trilinos / PETSc), и используют эту экосистему для получения преимуществ от использования кодов сообщества.


7

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

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

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


6

Мои 2 цента.

Я думаю, что об этом легче писать, чем о C / C ++. Во-первых, библиотеки в таких языках, как Python, не обязательно используются для получения преимущества в скорости, даже если это является следствием. Я думаю, что @David довольно хорошо объяснил причины.

Взяв это сверху, языковая реализация в некоторой степени определяет, к каким библиотекам у вас есть доступ. Обычно используемые языки в вычислительной науке включают C, C ++, Python, Perl, Java, Fortran и R. Менее распространенными примерами могут быть Ocaml и Common Lisp. Теперь, поскольку большинство этих языков написаны на C, они имеют естественный интерфейс с иностранными функциями для C. Однако не так просто вызвать, скажем, библиотеку Perl из Python или наоборот. Так что на практике люди склонны либо

  1. Использовать библиотеку, написанную на их языке реализации, обычно что-то, что является частью стандартных библиотек, или иным образом широко доступным, или

  2. Вызов библиотеки C / C ++ через языки FFI. Предполагается, что обертка еще не существует, поскольку, если она существует, ее трудно отличить от (1).

(2) обычно сложнее, потому что вы должны обернуть функцию C / C ++ самостоятельно. Кроме того, вы должны либо связать библиотеку, либо добавить дополнительную зависимость. По этой причине люди более склонны использовать библиотеки встроенного языка, а не использовать, например, GSL, который находится на C.

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

Таким образом, это становится проблемой оптимизации в анализе затрат / выгод. Мой опыт показывает, что даже для сравнительно стандартных методов, таких как MCMC, я обычно пишу свой собственный код, потому что он лучше согласуется с тем, как я разрабатываю общее программное обеспечение.

Конечно, даже если вы в конечном итоге не используете код, можно поучиться на чужом коде. Я не знаю, как часто ученые действительно пытаются это сделать. У меня сложилось впечатление, что чтение чужого кода для обучения - это, скорее, инженер-программист.


6

Вспоминая свой второй курс обучения механике, мне приходит в голову, что одна из причин, по которым я реализовал свои собственные версии хорошо известных алгоритмов, заключается в том, что меня учили так делать. Я не могу вспомнить ни одного примера, где меня учили, как взаимодействовать с библиотекой и связываться с ней в моем физическом образовании. У меня есть приятная память о том, что я впервые увидел список координат вращающегося мяча для гольфа, рассчитав решение связанных уравнений Ньютона в Фортране. Существует определенное волнение и удовлетворение (даже гордость), которое исходит от вычисления вещей с нуля.


1
Это, безусловно, фактор. И то, что нужно сосредоточиться на том, чтобы сделать это самостоятельно, необходимо для образования ученого в области вычислительной техники. Чистые программисты в какой-то момент выбивают их из колеи, но те, кто занимается наукой, могут переместиться прямо из этого вводного класса в проект, населенный почти исключительно другими людьми, которые шли по тому же маршруту.
dmckee

5

Я думаю, что следует использовать максимально проверенные библиотеки. Большинство людей не являются экспертами в области численных вычислений и, вероятно, не смогут реализовать решение так же правильно и тщательно, как то, что доступно в хорошо протестированных библиотеках. Тем не менее, иногда случается, что нет доступных библиотек, которые реализуют комбинацию возможностей, необходимых в данном приложении. Я видел это в технической области, в которой я работаю; существующие коды не решали все случаи, и кто-то в итоге внедрил решатель с нуля, который это сделал.


1
Если библиотека не покрывает все ваши потребности, я бы порекомендовал вам расширить код библиотеки и отправить исправление. Таким образом, вы принесете пользу многим другим в своей работе, и другие также протестируют ваш код для вас. Конечно, это предполагает, что код библиотеки был написан достаточно гибким образом, чтобы его можно было расширять в соответствии с вашими потребностями.
Дэвид Кетчон

Я согласен, это отличное решение, и что-то, что люди должны делать, если это вообще возможно.
Мхака

5

Основная проблема часто связана с интерфейсом между приложением и библиотекой. Прикладной программист знает о проблеме, которая часто теряется при передаче проблемы (например, в виде матрицы) в библиотеку. Это знание таково, что его использование более чем компенсирует преимущества использования высокооптимизированной библиотеки. В результате прикладной программист «катит» свою собственную реализацию, которая использует знания.

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


3

В дополнение ко всему, что уже было сказано выше, я повторю свой ответ на вопрос «Fortran vs C ++»: самый ценный актив, которым обладает программист, - это ее время. Да, внешние зависимости часто бывают неловкими. Но тратить время на повторную реализацию, отладку и тестирование алгоритмов, которые уже были реализованы другими, почти всегда глупо, и результат также редко будет таким же хорошим, как код, написанный экспертами по определенной теме. Повторно используйте то, что сделали другие!


Я даю свой ответ по этой теме. Вы можете узнать намного больше, переписав все детали. Я работаю уже 5-6 лет с облаками точек. Первые три года я сам писал все функции. Вторую половину я потратил на использование библиотеки Point Cloud. Я не могу доказать, но я считаю себя более сильным экспертом в PCL, проведя первые три года, думая о решениях, которые уже предоставили другие.
Ян Хакенберг

@JanHackenberg - да, но позвольте мне также быть тупым: вы только что потратили три года своей жизни, заново изобретая колеса. Представьте, сколько нового вы могли бы сделать, если бы использовали то, что сделали другие !?
Вольфганг Бангерт

Я решил написать на Java на первом курсе, потому что в то время считал, что мои навыки программирования (а не теория в информатике) близки к нулю. Ява была все еще тем языком, на котором я был лучшим на практике. Я также считал Java хорошим выбором из-за легкой многоплатформенной поддержки. Я поступил на кафедру без информационной поддержки в PhD (традиционное лесное хозяйство). Я перескочил на c ++, когда осознал свою ошибку и смог (после публикации, а не до).
Ян Хакенберг

Кстати, я не согласен с тем, что мне не хватило трех лет моей жизни. Это означало бы, что у меня было всего два полезных года работы в докторантуре. Сегодня я могу разместить 10 миллиардов цилиндров в облаке точек лесного хозяйства и позволить машине решать, какие из них хороши для представления деревьев. Мои ~ 50 пользователей тоже могут это сделать. Через ~ 1 час Все приемы я усвоил, усвоив тяжелый и трудоемкий способ. Я решил никогда не изучать, как использовать vi, но когда люди, проходящие необходимую кривую обучения, утверждают, что используют наиболее эффективный способ создания кода, я им верю.
Ян Хакенберг

2

Группа, с которой я работаю, максимально использует библиотеки. Я один из немногих программистов, а остальные люди взялись за программирование на работе. Они знают достаточно своих собственных ограничений, чтобы знать, где они не должны баловаться. IMSL является предпочтительной библиотекой. Такие вещи, как GSL, будут запрещены из-за лицензионных ограничений, даже если это федеральное агентство, и мы все равно отдаем наше программное обеспечение.


2

«Повторное использование - это в первую очередь социальный феномен. Я могу использовать чужое программное обеспечение при условии, что

  1. оно работает
  2. это понятно
  3. это может сосуществовать
  4. это поддерживается (или я готов поддержать это сам, главным образом я не)
  5. это экономично
  6. Я могу найти это.

"- Б. Страуструп, язык программирования C ++, 2 изд. (1991), с. 383.


1

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

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

Конечно, многое зависит от конкретного приложения и от того, сколько раз будет вызвана библиотечная процедура. Зачем вам вызывать библиотечную процедуру для функций Бесселя миллиарды раз, если вам нужно всего лишь несколько значащих цифр для небольшого диапазона x, и для ваших нужд будет достаточно более простой техники?


0

Немного добавить, мы должны повторно использовать код, речь идет об устойчивости кода и его вкладе в общество, но это все выше.

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

Очень часто в начале понимают метод, но не так, как он реализован в библиотеке, или просто как использовать библиотеку с ее универсальным интерфейсом, контролем ошибок и соглашениями, очень часто документируемыми для опытных программистов, если вообще. Это дает иллюзию, что лучше реализовать стандартный метод, такой как факторизация LU самостоятельно. Более того, новые программисты недооценивают ценность тестирования, валидации и переносимости кода для разных операционных систем. В конце концов, причина в лени, поэтому написание собственного кода выглядит как более быстрое и простое решение.

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

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


-1

Алгоритмы библиотек обеспечивают в отличие от собственных реализаций:

  • Они являются общими и шаблонными. Позже вы можете перепараметризовать вашу реализацию, не беспокоясь об изменении собственного кода, который должен иметь множество ограничений.
  • Имеются случаи сохранения исходных данных по сравнению с вырожденными. Многие алгоритмы вычислительной геометрии, например алгоритмы с выпуклой оболочкой, должны обрабатывать, например, коллинеарность трех точек. Возможно, вы сможете игнорировать эти случаи, если вы никогда не планируете распространять свой код, а также не хотите часто использовать его в будущем.
  • Они обеспечивают минимальную сложность времени выполнения для ожидаемых или наихудших конфигураций ввода. Алгоритмы более высокого уровня имеют в качестве строительных блоков часто алгоритмы более низкого уровня, например, алгоритмы сортировки или специальные типы данных. Быстрая сортировка может быть наиболее распространенным выбором для сортировки данных, но если ваша реализация алгоритма должна гарантировать n (log (n)), вы не сможете его использовать.
  • Они эффективно используют память
  • Они дополнительно оптимизированы во время выполнения
  • Если поддерживается, то гораздо более закрытым, чтобы быть «без ошибок» в целом, особенно если вы работаете с основной веткой. Нет ничего более проверенного, чем хорошо распределенная библиотека. Не каждая ошибка дает сбой, не каждая ошибка дает необоснованные результаты. Реализация вашего алгоритма может по-прежнему давать приемлемые результаты, но не так хорошо, как это было задумано. Чем менее заметна ошибка, тем меньше вероятность того, что вы, как один человек, сможете ее обнаружить.

Я по-прежнему считаю правильным при вводе нового поля самостоятельно реализовать одну версию хорошо понятного алгоритма. Я занимаю много времени в общей сложности. Я покупал и читал книги, названные Press et al. Я всегда читаю много теорий до и во время этих реализаций. И после понимания общих концепций поля и практического применения ловушек для меня пришло время перейти ко всем аспектам лучшей реализации библиотек. Я думаю, что вы станете лучшим пользователем библиотеки, если вы напишете алгоритм "привет мир" в поле библиотек самостоятельно.

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

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

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

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


1
Нелепо думать, что «реализация алгоритма самостоятельно» и «изучение синтаксиса библиотеки» будут «стоить одинаково». Это даже не верно для простых функций, таких как "strcat". Это совершенно определенно не относится ко всему, что находится, например, в LAPACK или в библиотеках более высокого уровня.
Вольфганг Бангерт

@WolfgangBangerth Спасибо за отзыв. Я перечитал то, что написал, и не хотел передавать сообщение о том, что собственные реализации могут быть конкурентоспособными. Но я так много узнал. Мои «оба могут стоить две недели» не был хорошим примером. На самом деле это стоило мне в последний раз «изучения синтаксиса» 2 недели, когда я перешел с формы Java на C ++, и я также выучил базовый синтаксис C ++ в это время. Я больше боролся с указателями, чем с моей новой библиотекой. Две недели на любом из моих реализованных алгоритмов, возможно, были временем написания кода, что было моей незначительной инвестицией (чтение книг раньше занимало намного больше времени).
Ян Хакенберг

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

@WolfgangBangerth Я согласен со всеми вашими аргументами. Единственное, что я хочу сказать, это то, что вы узнаете гораздо больше теории, когда вам нужно разобраться с этими угловыми случаями самостоятельно. Моя первая версия моего ответа действительно звучала так, как будто ничего не изменилось. Я был ужасно устал. В улучшенном ответе я больше пишу о преимуществах стабильности библиотек. Для меня это компромисс между затраченным временем и заработанными знаниями.
Ян Хакенберг
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.