Pull to refresh
0
0
Дмитрий @qrKot

Энтузиаст

Send message

доступ к которой выполняется через границу сети.

может, пограничные сети?

не бывает так. Любые отчисления идут последовательно. Если 30% от суммы продаж, то Valve будет брать свою долю от уменьшенной суммы, комиссия за перевод будет от суммы перевода и т.д. С российской стороны налог будет браться уже от того остатка, который пришёл на счёт. Дважды обложить отчислением одну и ту же сумму нельзя

Крутая штука. Интересно, когда до домашнего сегмента 3д печати доберется?

туго связанные

почесал промежность и понюхал руку

Машинный перевод, шуточки за 300 и отсутствие вменяемого ответа на вопрос из первого абзаца.

пошаговый план заработка 100к рублей на чат ботах и автоворонках начиная с нуля.

Срок обучения – 3 часа.

Цена – 990 рублей.

Вы серьезно? Это же просто подборка инфоцыганских курсов! Что это делает на хабре?

По идее, если отстраниться от различия между кооперативной и преемптивной асинхронностью (т.е. наличием/отсутствием явного await), tokio::spawn работает точно так же, как ключевое слово go

Не совсем. tokio::spawn - паттерн actor, go - csp.

Вот тут есть сравнение подходов: https://en.wikipedia.org/wiki/Communicating_sequential_processes

Прям раздел есть Actor vs CSP

Вкратце:

  • актор - управляемый, CSP - нет.

  • csp - обмен сообщениями эксклюзивно через каналы

  • actor позволяет отправку сообщения с отложенной обработкой, csp - блокирует отправку, пока читатель не готов к получению.

Из побочек: Rust, видимо, стектрейс ломать не дает. Т.е., фактически, мы не можем завершить выполнение вызывающего метода (как минимум стек вызова чистить нельзя), пока заспавненный не завершился + мы иерархию задач имеем. В Go - ипанул и забыл.

tokio::spawn работает точно так же, как ключевое слово go.

tokio:spawn сложнее, чем go . Горутины легче.

Неа. tokio::spawn создаёт task-у, которая может быть запущена в любом потоке из имеющегося пула.

Да в смысле "неа"?

У вас async внутри tokyo::spawn! Буквально M асинхронных задач внутри одного потока, M:1 внутри 1:N.

Асинхронные задачи, которые будут выполняться внутри этой таски, исполнятся ровно в том потоке, в котором таска запущена!

В Go - любая асинхронная задача может выполниться на произвольном потоке.

M:N - задачи, закинутые в spawn, распределяются по всем доступным ядрам, если не сказать прямо "работаем только в текущем потоке".

async при этом явно говорит "работаем в текущем потоке".

tokio::spawn(async {

Присмотритесь внимательно:

tokio::spawn - 1:N

async {} - M:1

Это не M:N, это, по сути, от thread::spawn отличается только наличием "легковесного потока".

То, что вы написали - это "запустить поток, внутри которого выполнить асинхронный код". Т.е. это будет N раз по M:1. Это не то же самое, что M:N.

Просто для ясности ситуации:

Допустим, вы спавните (tokio::spawn) 2 потока, должны дождаться выполнения обоих и с полученными данными дальше что-то делать. В первом вы асинхронно запускаете 10 задач, во втором - 2. Допустим, время выполнения асинхронной задачи константно, берем за единицу.

За сколько выполнится ваш код? Очевидно, за 10 (потому что 10 задач на одном потоке выполнялись, а второй выполнил 2 и благополучно сдох).

Теперь представим, что у нас 2 потока доступно гошному рантайму (на самом деле мне, как разработчику, откровенно насрать, сколько их там, я могу и имею право даже не знать). Мы запустили 10 горутин в одном блоке и 2 - в другом. Итого 12. Делим на 2 потока - сделали за 6.

Все, на этом "фишки" языка Go окончены. Он создан ровно ради этой фичи. Во всех остальных сценариях Rust круче, C# круче, Java кручке, все круче (кроме, простихоспади, PHP, Basic'а и "Языка программирования 1С" ).

Я не покажу, я в расте так себе. Но скорее всего, оно M:N, потому что токио - мультитредовый рантайм.

Да, токио - мультитредовый рантайм, и нет, оно не M:N. Оно 1:N.

async/await - M:1, tokyo - 1:N. M:N, кроме Go, из коробки, вроде, Haskell только (или Erlang, не помню, не большой специалист в хаскелях и эрлангах) и умеют. Собственно, весь язык ровно из плясок над "M:N с человеческим лицом" и состоит. Все, что мешает этой концепции - совершенно обоснованно идет лесом. Исключения, например, одна из вещей, которая мешает.

Как типизация влияет на 1:N/M:1/M:N? Разговор был о том, что в го так себе реализованы ошибки.

Типизация, в общем смысле, не мешает. А try/catch, ? и прочие примитивы обработки исключительных/ошибочных ситуаций - мешает. Я вот про это говорю.

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

Дык, там и M:N нету. Другие языки решают другие проблемы.

В Go нет исключений и они вряд ли там к месту. Забейте уже на них)

Я бы уже давно забил, если бы вы мне в каждом первом комментарии не рассказывали, какие они классные в C#, и почему в Go они прямо необходимы.

Читаемость роняет мусорный бойлерплейт.

Все же, еще раз про исключения: в го они этим самым мусорным бойлерплейтом и станут, без вариантов.

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

Еще раз приведу примеры из стандартной библиотеки, где это делается.

Еще раз говорю, это не проброс ошибки, а возврат значения.

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

И это не хорошо и не плохо, это просто цена 100-тысячных RPS.

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

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

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

На выходе мы имеем 2 сценария групповой обработки ошибок/исключительных ситуаций:

  1. обработать те, с которыми мы знаем, что делать, остальные уйдут по стеку наверх (exception-style) с падением приложения при выходе за границы стека - неприменимо в Go, т.к. наличие "наверх" никто не гарантирует. Мы вырождаемся в обязанность никогда не возвращать ошибки, что выглядит как бред.

  2. гарантировать обработку всех ошибок (назовем это rust-style) - ну, какбэ, бойлерплейт по обработке рискует превратиться в портянку такого размера, что if err != nil {} покажется цветочками.

А потом поняли, что так жить грустно и начали фичи завозить.

Фичи завозят, стоит признать, аккуратно, дозированно, и только те, которые признаны удачными.

Богатый синтаксис - это ведь круто.

А вот тут - не всегда. Смотрел списки фич в Python'е, чейнджлоги того же PHP... А работать когда?

Ошибка возникает в parse_string_to_smth. Что должен делать parse_file? На месте обработать? Кинуть панику или вернуть что-то пустое? 

Если знает, что с ошибкой делать - обработать на месте. Если не знает - обернуть своей ошибкой и вернуть. Панику - а зачем? Ну, если нужна паника - кинуть панику. В чем проблема-то?

Так это почти всегда (если нормально делать) не зона ответственности этой функции.

Ну, я и говорю:
если не зона ответственности

  • if err != nil { return nil, err }

  • if err != nil { return nil, fmt.Errorf("%w: some package error", err) }

  • if err != nil { return nil, errors.New("error parsing string") }

если зона ответственности: if err != nil { // do something }

И в одном случае там может быть просто варнинг, потому что данные из файла вспомогательные и у нас есть какое-то дефолтное значение. А во втором случае надо паниковать, потому что данные из файла необходимы.

А вот тут вы, внезапно, не очень умную вещь сказали.

"потому что данные из файла" - тут, как минимум, "из файла" - лишнее.

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

А дальше, собственно, от требований пляшем.

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

var ErrShitHappened = errors.New("shit happens")
var ErrWeCanIgnore = errors.New("ignore this")

func readData(someParamsHere...) (any, err) {
  if err := openResource(); err != nil {
    return nil, fmt.Errorf("%w: can't open resourse %w", ErrShitHappens, err)
  }
  if err := parser.ParseResource(); err != nil {
    if errors.Is(err, parser.ErrCritical) {
      return nil, fmt.Errorf("%w: parser failed with %w", ErrShitHappens, err)
    }
    return nil, ErrWeCanIgnore
  }
  if len(result.Rows) == 0 {
    return nil, ErrWeCanIgnore
  }
  .....
  return value, nil
}

func business() {
  data, err := readData(someParams)
  if err != nil && errors.Is(ErrShitHappened) {
    panic(err)
  }
    ...
}

Так что обычно как-раз пробрасывают ошибку от "утилитарной" до какой-то более "сведущей" функции.

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

Я хз, может в го не так, там свой way, надо всегда паниковать или nil вместо значений возвращать

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

, но почему тогда я в го-коде вижу if err != nil { return err } ?

Потому что таков путь, и в 99% случаев этого достаточно. Можно бы было забить, но все еще есть 1%, где этой конструкции не хватает. Ну да, такова жизнь, более удачных решений не наблюдается (и не надо опять поднимать песню про исключения. Они а) сами далеки от идеала, б) полноценно нереализуемы в парадигме Go (а эрзац - нафиг не нужен).

Погуглите пропозалы

Да читал я эти пропозалы! Про групповую обработку ошибок - штук 5, наверное, читал. Во всех есть рациональное зерно, и все, в конечом итоге, не работающие в совокупности с другими элементами языка.

что ж вы так вцепились в исключения?

Вы мне их активно сватаете, я отбиваюсь.

Тем более вы кроме if err != nil в языке других проблем не видите. А они там есть, причем в ассортименте, и гораздо более важные, чем "среднее количество букв, уходящее на обработку ошибок".

Достойный довод, чтобы не язык не развивался)

Дык, в том и дело, что развивается.

Кастомные ошибки - завезли (возможность однозначной идентификации ошибки и матчинга ошибок по стеку с целью более гибкой обработки - гораздо более важная штука, чем уменьшение количества if err != nil )

Дженерики - завезли (тоже фича сильно более интересная, чем неработающий try/catch).

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

Решаются те проблемы, которые а) можно решить, б) важно решить.

Все необходимое там есть и активно используется.

Ключевое слово return - это возврат, а не проброс. Вы же понимаете, что возврат и проброс чем-то семантически различаться должны? Не только ключевое слово throw вместо return, но и причина, по которой это слово появилось. Оно должно делать что-то другое, отличное от того, что return делает.

throw Exception - выбросить исключение, не обработать исключение в текущем методе - пробросить исключение.

if err != nil { return err } - это не проброс, а закат солнца вручную какой-то. Нету в Go механики выброса, проброса, переброса, пофигчегоброса, есть только возврат.

Rust, где система обработки ошибок не идеальная

Ну вот как станет идеальная, так сразу и скопипастим целиком!)

В C# есть исключения

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

Но обработка ожидаемых ошибок там тоже не вызывает восхищения.

Вот и я говорю, что ровно тот же хрен, просто в другой руке.

Хотя есть некоторые приятные фичи, вроде кастомных implicit cast'ов, которые могут действовать, как трейт From<T> в Rust.

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

Везде пробрасывают. И даже в Go пытаются.

Каким образом в Go пытаются, если в языке нет возможности проброса? Вернуть значение - пожалуйста, что-то пробросить - ваще без вариантов.

// Запускаем параллелльно

1:N

// Запускаем асинхронно

M:1

M:N покажете?

thread::spawn

Системный тред? Вот тот самый, который 12МБ памяти на старте просит? С отдельным коллстеком? А тысячу сможем запустить? А 10 тысяч?

Вы говорите, что исключения - ерунда неудобная

Я говорю, что в контексте Go роль исключений сводится к синтаксическому сахару над if err != nil {} (т.е. полезность в контексте применимости в Go - очень спорна)

отсутствие в Go try-оператора

Не надо он там, только читаемость роняет.

кастомных implicit cast'ов

Явное лучше неявного, имхо. Палка о двух концах, которая а) может больно ударить (особенно в контексте достаточно куцей системы типов в Go), б) шаг в сторону слабой (нестрогой) типизации. Очень дискуссионно, причем вы - на позиции "я так привык".

типов-сумм

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

Что мешает пробрасывать ошибки в вызывающую функцию?

Да блин, сколько повторять-то? ОТСУТСТВИЕ ВЫЗЫВАЮЩЕЙ ФУНКЦИИ очень сильно мешает пробрасывать в нее ошибки! Ну хоть одним глазком посмотрите, как горутины устроены, и что такое CSP. Не обязательно все читать, хотя бы просто тезисно пройтись.

или крайности: либо суровая явная явность

Дык, прямо лозунг Go "Суровая Явная Явность". С самого начала так декларировалось.

полностью неявный флоу исключений

Полностью явный флоу исключений называется Checked Exceptions. Его критикуют, сильно и небезосновательно.

то это будут разные (вложенные) try-блоки, аналогично работе с исключениями

Но ведь именно в этом (пусть и вырожденном) случае try/catch более многословен и даже местами некрасив.

if err := someFunc(); err != nil {
  return err
}
try {
  someFunc();
} catch e Exception {
  throw e;
}

И кто тут более многословный?

Нет, в расте могу их поднимать наверх насколько хочу

Ну вот только в Расте и можете.

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

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

То есть в любом языке с легковесными тредами нет исключений?

В любом языке с горутинами нет исключений. Потому что концепция горутин напрямую противоречит концепции исключений.

Да и "любых языков с легковесными тредами" не так много. Для статистики откровенно маловато. Где там CSP из коробки еще в языке есть, кроме Go? В Limbo, разве что. Но там тоже с исключениями не богато, вроде?

А почему в ерланге они тогда есть?

Потому что Erlang предоставляет модель акторов из коробки, а Golang - CSP? Потому что не перехваченное исключение в эрланге уронит актора, а в Go - все приложение? Потому что в эрланге легковесные потоки не шарят стейт между собой, а гошные горутины напрямую шарят память? Каких еще аргументов надо?

Не "ради", исключения в C# - это родной инструмент

В C# - родной. Вы же сетуете, что такой штуки в Go нет. Ну вот в Go, окромя обязательного перехвата всех исключений в конце каждой функции, вариантов нет. Полезная нагрузка - возможность обработать исключительные ситуации пачкой. Геморрой - обязанность обработать все исключительные ситуации в конце каждой функции.

Т.е. вы бесячий if err != nil {} предлагаете на ровно такой же try/catch Exception {} поменять.

По идее, ничто не мешает задизайнить в языках такую штуку для легковесных типов: аналог try-оператора, который позволял бы типу "всплывать", но не из функции, а в до ближайшего catch внутри вызывающей функции. То есть чисто синтаксический сахар над легковесными типами ошибок в стиле исключений, но без их затрат на раскрутку стека и собирание бэктрейсов.

При этом, ни в одном языке такого не сделали... Может, единогласно решили, что оно не надо?

Например, потому что ошибка NotFoundInDatabase , полученная из какой-нибудь CheckUserValid и CheckUserBalance должны, все равно, быть обработано по-разному?

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

Тут вопрос в том, что в довесок - обязанность обязательно обработать исключение в конце функции.

Исключения можно безболезненно ловить выше

Дак я вам еще раз говорю: нельзя, нету никакого "выше". Мы в Go.

а try-функции или аналогичные конструкции - для ситуативного, что необходимо обрабатывать тут же.

А это в Go есть. Он именно так и делает: обрабатывает "тут же".

Понимаете, 2 флоу: исключения и ошибки. Ошибки везде (включая Go) обрабатываются на месте, а исключения в Go не работают, потому что горутины.

Не каждая ошибка - это исключительная ситуация. Поэтому для них отдельный флоу, который обладает супер-способностями, но не очень подходит для прочих ошибок.

... а флоу обработки "прочих ошибок" в других языках не отличается от Go-шного... В C# ситуация, получается, ровно такая же, как в Go, но там - красиво, а здесь - нет...

Альтернативы-то какие?

Так нужна не альтернатива, а допиливание имеющегося. Чтобы в Go тоже можно было пробрасывать ошибки выше без боли.

Это Go, "выше" может не быть. Куда пробрасывать-то? И, самое главное, зачем? Нигде не пробрасывают, а в Go всенепременнейше надо!

Вау, снимаю шляпу! Мсье знает толк...

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

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

Кортеж с ошибкой на C#

Ну, и что следует из того, что в C# можно сделать такую же обработку ошибок, как в Go? Удобство-то где?

Лол. Вы сначала этот сахар нарекаете "исключениями", а потом говорите с такой экспрессией, будто я предлагал сделать настоящие исключения в Go.

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

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

Придумают удобный синтаксис обработки (пока нигде не придумали) - затащат в язык. Не придумают - ну, будем страдать вместе со всеми.

Information

Rating
Does not participate
Location
Бийск, Алтайский край, Россия
Date of birth
Registered
Activity