Всё просто, либо вы тащите с собой этот патч до скончания веков — накладываете его на новые версии библиотеки, чините мерж-конфликты — либо держите устаревшую версию с возможными багами, уязвимостями и проблемами совместимости с новыми системами, героически превозмогая ещё и это.
Обычно хорошие разработчики стараются всего этого избежать, поэтому пропихивание патча в апстрим решает чисто ваши собственные проблемы.
Вообще, partial в питоне — это не каррирование, а частичное применение, это разные вещи.
Например, вот каррирование в OCaml:
# let add x y = x + y;;
val add : int -> int -> int = <fun>
# let add2 = add 2;;
val add2 : int -> int = <fun>
# let sum = add2 3;;
val sum : int = 5
А вот частичное применение в питоне:
>>> add = lambda x, y: x + y
>>> add
<function <lambda> at 0x7f78aba64aa0>
>>> add2 = partial(add, 2)
>>> add2
<functools.partial object at 0x7f78aba57520>
>>> sum = partial(add2, 3)
>>> sum
<functools.partial object at 0x7f78aba577e0>
>>> sum()
5
Т.е. мы получаем всё ещё функцию, а не результат, более того, мы можем навесить ещё один partial с какими-нибудь аргументами на функцию sum и узнаем об ошибке только в момент вызова полученной новой функции.
А нужно это во многих местах: например, если у какого-нибудь объекта (кнопки, сокета, whatever) подписка на событие требует передать функцию-обработчик, принимающую один аргумент, например, событие, а у вас более сложный обработчик, которому вы хотите передавать больше информации:
def complex_handler(obj, logger_name, times_to_log, event):
for _ in range(times_to_log):
logging.getLogger(logger_name).info("got event from %s: %s", obj, event)
obj1.subscribe(partial(complex_handler, obj1, "incoming", 3))
obj2.subscribe(partial(complex_handler, obj2, "outgoing", 5))
Посмотрите, как это делается в стандартной библиотеке:
https://doc.rust-lang.org/src/core/option.rs.html#667
https://doc.rust-lang.org/std/collections/hash_map/enum.Entry.html#method.or_insert
Я тоже ненастоящий сварщик, но в моём понимании у функции должно быть место, в котором она говорит ret и вызывающему коду возвращается то, что лежит в соответствующем регистре. При TCO бесконечного цикла в коде просто не остаётся ret, который можно было бы позвать. Можно пытаться его как-то эмулировать, воткнув никогда не вызываемый ret после джампа, но это нелегально, потому что у нас нет никакого значения заявленного типа (CowVM), которое можно положить в регистр для возврата.
При этом можно явно заявить функцию, как не возвращающую значение:
Конкретно у этого примера, как вам правильно заметили, нет никакого условия выхода. Если на него внимательно посмотреть, то получается, что такой код завязан на переполнение i32 (не помню, есть ли в Rust про это какой-то стандарт, а в C это называлось бы UB), т.е. ваш код выйдет, когда мы переполним i32 и дойдём до 0 с отрицательной стороны. При таких условиях компилятор имеет право нарисовать на экране котика вместо заданного кода. Например, если убрать ваш println!, то вся функция при компиляции превратится в одну строчку: ret i32 1.
Если же говорить о вашем цикле из статьи, то вероятно, что ему тоже просто необходимо корректное условие выхода из цикла, потому что в функции execute его вообще нет. Мне лень лезть в код, но я подозреваю, что у вас предполагается, что в какой-то момент получение следующей инструкции выкинет панику или прямо сделает abort(), но это как раз плохой и нефункциональный подход.
Другое дело, что из вашего кода непонятно, то ли вы его неправильно написали, так как первоначальный вариант функции execute содержит точку с запятой в самом конце, что подразумевает, что последняя инструкция не новый execute(...), а просто (), пустая инструкция, такой код вообще по идее не должен скомпилироваться, потому что возвращаемое значение не совпадает с сигнатурой функции. То ли по какой-то иной причине не отработала оптимизация.
Разумеется, Вы неправы, и тому есть одна простая фундаментальная причина.
Пушкин гений и великий классик не потому, что он как-то особенно точно выбирал слова. Это следствие. А причина заключается в том, что именно Пушкин изобрёл весь тот современный литературный язык, которым стали писать после него. Его до него просто не было.
Сравните. Вот поэзия XVII века:
Что ся ныне в нашему веку сотворило,-
то много ся злых дел расплодило.
Яко едва ж ести и нам у бога милости место,
коль тяшко от бояр народу и тесно.
Только умножися гордость и кривды.
яко ж и отнюдь не знают суда и правды.
Древних бо княжат уставы отвергоша
и вся древния судебники обругаша,
новые ж своя вся суетне поставляют,
и тем бесчисленно народу укривжают.
Вспомните, например, Ломоносова (всего полвека до Пушкина) с его идеей штилей и тяжёлым нечитаемым языком:
К словесным знаниям прехвальная охота.
Природный видит твой и просвещенный ум,
Где мысли важные и где пустых слов шум.
Мне нужен твоего рассудок тонкий слуха,
Чтоб слабость своего возмог признать я духа,
Когда под бременем поникну утомлен,
Вниманием твоим восстану ободрен.
И Пушкин, которого можно читать как обычный современный разговорный текст:
Мать и сын теперь на воле;
Видят холм в широком поле,
Море синее кругом,
Дуб зеленый над холмом.
Сын подумал: добрый ужин
Был бы нам, однако, нужен.
Ну ок, давайте на этом и остановимся. Я правда в этом треде кажется нигде не говорил про «плюсы плохие», но да, они разрешают стрелять в ногу, поэтому сделали не так.
Есть ещё варианты в стиле «человек с опытом паскаля решил помочь в опенсорс-проекте или на работе перекинули за отсутствием других возможностей», вполне себе типичная ситуация, не раз видел. Синтаксис знакомый, общие принципы можно по-быстрому где-то прочитать, сел и пошёл писать. Или этот ваш «начинающий профессионал» профукал и забыл этот момент в учебнике, не все же отличники.
> Это уже не для новчика.
Ну, не знаю, мне кажется, что новичок вполне может сразу гуй писать. Это студенту такое не дадут, а заставят до посинения писать алгоритмы сортировки, но студент != новичок.
> Это, конечно, может быть проблемой
Главная на самом деле проблема — это то, что такое знание энфорсится в программиста. Т.е. это правило написано в каких-то книжках, на любом форуме вам авторитетно объяснят, что вы идиот и должны сначала были читать учебник, но в лучших традициях C++ требование не прописано в стандарте, программисту разрешается стрелять в ногу «и так тоже», и вся надежда на добрую волю IDE и компилятора с их подсказками (которые программист конечно же не факт, что прочитает или не проигнорирует).
> Ресурсов это не сожрет.
Как же не сожрёт, когда сожрёт? :) И размер объекта вырастет:
0 ➜ compileOnce 'class A { public: ~A(){}}; class B { public: virtual ~B(){}}; std::cout << sizeof(A) << std::endl << sizeof(B) << std::endl;'
1
8
И производительность ухудшится (накладные расходы на походы в vtable). Другое дело, что важным это замедление станет ещё хрен знает когда, но факт есть :)
А ещё я видел библиотеку, где объявление реализации интерфейсного метода с virtual приводило к сегфолту! (т.е. в интерфейсе он был не virtual). Но это уже совсем другая история.
Обычно хорошие разработчики стараются всего этого избежать, поэтому пропихивание патча в апстрим решает чисто ваши собственные проблемы.
Зависть. Понимаю, что опыт и фантазия, но всё равно зависть.
Например, вот каррирование в OCaml:
А вот частичное применение в питоне:
Т.е. мы получаем всё ещё функцию, а не результат, более того, мы можем навесить ещё один partial с какими-нибудь аргументами на функцию sum и узнаем об ошибке только в момент вызова полученной новой функции.
А нужно это во многих местах: например, если у какого-нибудь объекта (кнопки, сокета, whatever) подписка на событие требует передать функцию-обработчик, принимающую один аргумент, например, событие, а у вас более сложный обработчик, которому вы хотите передавать больше информации:
Не так красиво, но время жизни закончится там, где надо.
https://doc.rust-lang.org/src/core/option.rs.html#667
https://doc.rust-lang.org/std/collections/hash_map/enum.Entry.html#method.or_insert
Он умный, такие проверки уже сто лет как есть.
Вероятно, это баг или разработчики ещё не оптимизировали это место в компиляторе. Попробуйте им зарепортить.
При этом можно явно заявить функцию, как не возвращающую значение:
И тогда компилятор штатно сгенерирует в конце функции:
И при этом компилятор как раз предупредит о проблеме:
warning: function cannot return without recurring --> a.rs:2:1 | 2 | / pub fn test(a: i32) -> ! { 3 | | println!("a"); 4 | | test(a + 1) 5 | | } | |_^ | = note: #[warn(unconditional_recursion)] on by default note: recursive call site --> a.rs:4:5 | 4 | test(a + 1) | ^^^^^^^^^^^ = help: a `loop` may express intention better if this is on purposeЕсли же говорить о вашем цикле из статьи, то вероятно, что ему тоже просто необходимо корректное условие выхода из цикла, потому что в функции execute его вообще нет. Мне лень лезть в код, но я подозреваю, что у вас предполагается, что в какой-то момент получение следующей инструкции выкинет панику или прямо сделает abort(), но это как раз плохой и нефункциональный подход.
0 ➜ cat a.ll ; ModuleID = 'a.cgu-0.rs' source_filename = "a.cgu-0.rs" target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128" target triple = "x86_64-unknown-linux-gnu" ; Function Attrs: nounwind readnone uwtable define i32 @test(i32) unnamed_addr #0 { start: br label %tailrecurse tailrecurse: ; preds = %bb4, %start %.tr = phi i32 [ %0, %start ], [ %1, %bb4 ] switch i32 %.tr, label %bb4 [ i32 0, label %bb7.loopexit i32 7, label %bb7.loopexit2 ] bb4: ; preds = %tailrecurse %1 = add i32 %.tr, -1 br label %tailrecurse bb7.loopexit2: ; preds = %tailrecurse br label %bb7 bb7.loopexit: ; preds = %tailrecurse br label %bb7 bb7: ; preds = %bb7.loopexit, %bb7.loopexit2 %_0.0 = phi i32 [ 43, %bb7.loopexit2 ], [ 42, %bb7.loopexit ] ret i32 %_0.0 } attributes #0 = { nounwind readnone uwtable }Другое дело, что из вашего кода непонятно, то ли вы его неправильно написали, так как первоначальный вариант функции execute содержит точку с запятой в самом конце, что подразумевает, что последняя инструкция не новый execute(...), а просто (), пустая инструкция, такой код вообще по идее не должен скомпилироваться, потому что возвращаемое значение не совпадает с сигнатурой функции. То ли по какой-то иной причине не отработала оптимизация.
Пушкин гений и великий классик не потому, что он как-то особенно точно выбирал слова. Это следствие. А причина заключается в том, что именно Пушкин изобрёл весь тот современный литературный язык, которым стали писать после него. Его до него просто не было.
Сравните. Вот поэзия XVII века:
Вспомните, например, Ломоносова (всего полвека до Пушкина) с его идеей штилей и тяжёлым нечитаемым языком:
И Пушкин, которого можно читать как обычный современный разговорный текст:
До Пушкина такого слога не существовало.
Набор .exe, .dll и .bat-файл для упаковки как исходники. Круто)
Ну, не знаю, мне кажется, что новичок вполне может сразу гуй писать. Это студенту такое не дадут, а заставят до посинения писать алгоритмы сортировки, но студент != новичок.
> Это, конечно, может быть проблемой
Главная на самом деле проблема — это то, что такое знание энфорсится в программиста. Т.е. это правило написано в каких-то книжках, на любом форуме вам авторитетно объяснят, что вы идиот и должны сначала были читать учебник, но в лучших традициях C++ требование не прописано в стандарте, программисту разрешается стрелять в ногу «и так тоже», и вся надежда на добрую волю IDE и компилятора с их подсказками (которые программист конечно же не факт, что прочитает или не проигнорирует).
> Ресурсов это не сожрет.
Как же не сожрёт, когда сожрёт? :) И размер объекта вырастет:
И производительность ухудшится (накладные расходы на походы в vtable). Другое дело, что важным это замедление станет ещё хрен знает когда, но факт есть :)
А ещё я видел библиотеку, где объявление реализации интерфейсного метода с virtual приводило к сегфолту! (т.е. в интерфейсе он был не virtual). Но это уже совсем другая история.