Re[2]: Никогда не недооценивайте силу по умолчанию
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 12.09.22 10:27
Оценка: +8
Здравствуйте, vsb, Вы писали:

C>>Однако, иногда нужны были бы модификаторы. Вопрос такой, какое поведение вы выбрали по умолчанию, а какое с модификатором из следующих категорий?


C>>Константность:

C>>const / mutable

vsb>const. Параметры функций не могут быть mutable вообще.


Почему? Я не могу например счётчик попыток принять и уменьшать?
(Я понимаю, что скопировать нет проблем, но всё же)

vsb> А скорей я бы отказался от отдельного ключевого слова для объявления переменной, переменная объявляется сама при первом упоминании.


Ты серьёзно??
Во всём коде функции используется kptm2, в одном месте kmpt2. Удачи в поиске. Особенно в конце спринта с рычащим тимлидом.
Блин, как я когда-то с этим с Фортране натрахался... (старом, конечно, в новых `implicit none` и вопрос решён). Сейчас за такие идеи только ломом по кумполу.
The God is real, unless declared integer.
Re[5]: Никогда не недооценивайте силу по умолчанию
От: maxkar  
Дата: 19.09.22 13:09
Оценка: 102 (2)
Здравствуйте, SkyDance, Вы писали:

SD>Дело в том, что "синхронности" в реальном мире не существует. Это абстракция, появившаяся в силу ограничений человеческого восприятия.

Синхронность в реальном мире как раз существует. Для человека оно соответствует "фокусу внимания" (еще в литературе иногда встречается локус внимания/locus of attention). То, что попадает в фокус — синхронно. То, что не попадает — асинхронно. У меня по этому поводу есть иллюстрация из моих слайдов по монадам. Допустим, наша задача — научить друга делать торт по определенному рецепту. Это можно делать разными способами. Например, вы можете давать инструкции, наблюдать за процессом выполнения и потом давать новые инструкции. С вашей точки зрения данное исполнение синхронно (у вас внимание сконцентрированно на друге и его действиях). А можно дать ему инструкцию вида "взбивай белки, когда закончишь — позвони, я дам следующую инструкцию". В этом случае выполнение для вас асинхронно, вы можете переносить внимание на другие операции.

Из примера выше следует интересное наблюдение. Люди неплохо умеют оперировать с абстракциями высокого порядка (higher kinded polymorphism)! Рецепт, записанный на бумаге ("взбить яйца, добавить муки, ...") является именно таким высокоуровневым алгоритмом. Два варианта выполнения с использованием друга я привел выше. Можно выполнять процесс самому. А мастер-класс по приготовлению вообще будет выполнением в стиле SIMD! Общее во всех этих сценариях не синхронность или асинхронность, общая часть — это последовательное выполнение (sequential execution).

Что еще можно сказать про людей? Понятие "асинхронности" настолько распространено, что для его выражения существуют специальные языковые конструкции. В русском языке это деепричастия и деепричастные обороты. "Тихонько напевая, Наташа взбивала белки". Здесь очень явный фокус на основное (взбивание) и асинхронное/побочное (напевание) действия. Есть и более параллельный вариант: "Наташа громко пела Интернационал и яростно взбивала белки". Как видите, никаких проблем с асинхронностью на самом деле нет. Как минимум — на уровне независимых высокоуровневых действий.

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

Есть и некоторые проблемы при координации действий нескольких людей. Ну например, охота на мамонта (пойдут и любые другие групповые действия). Здесь у нас нет конфликтов (contention) по владению, но может быть взаимовлияние (interference). Традиционное эволюционное решение — создание иерархии. Появляется руководитель (лидер, дирижер и т.п.), который координирует остальную команду. Он в некоторой степени знает, что делает каждый участник. Что само по себе предоставляет еще одну проблему — здесь нет инкапсуляции! Люди не умеют разбивать задачу на "цели", они разбивают ее на "последовательности координированных действий". А эта модель не очень хорошо ложится в бытовые представления об асинхронности.

Т.е. можно выделить следующие проблемы асинхронности:

SD>Синхронный вызов есть ни что иное как жесткая связка call + return.

Это спорный вопрос. Здесь есть противоречие между различными уровнями абстракций. Есть "логическая" операция, и есть "физическая". Например, "запечь курицу" состоит из "поставить курицу в духовку" (синхронное действие) и "дождаться готовности" (асинхронное). В программах это различие тоже может быть важным. Например, мы хотим предложить пользователю магазина товары, которые он уже покупал. Для этого нам нужно загрузить данные о предыдущих покупках и инвентарь (чтобы не предлагать товары, которых нет в наличии). В этом случае очень хочется явно различать начало и возврат:
val ordersOp = userService.getRecentOrders(user.id); //Async[Seq[Orders]]
val inventoryOp = inventoryService.getCurrentInventory(); //Async[Inventory]

val orders = await ordersOp;
val inventory = await inventoryOp;

val items = orders.flatMap(_.items).filter(item => inventory.getCount(item.id) > 0);

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

SD>Синхронность нужна лишь для того, чтобы упростить логическое доказательство корректности (что называется, easier to reason about).

Во многих случаях нужна именно последовательность, не синхронность. Синхронность выполнения здесь ничем не помогает. Если у нас есть несколько потоков (и нет явного владения), то у нас могут возникать гонки (race). А если всего один "поток выполнения", то гонок нет. При этом вполне может быть "асинхронное последовательное выполнение". Например, среда выполнения может вместо блокирующих вызовов IO внутри делать неблокирующие и прекращать интерпертацию программы до лучших времен.

И еще один пример "последовательного" вместо "синхронного". Классический javascript (который однопоточный) и его promises против for comprehension/do notation. Вложенные then читать гораздо сложнее, чем for. Сравните:

let ordersOp = userService.getRecentOrders(user.id); //Async[Seq[Orders]]
let inventoryOp = inventoryService.getCurrentInventory(); //Async[Inventory]

ordersOp.then(orders => 
  inventoryOp.then(inventory =>
    let items = ...
  )
)

или
for {
  orders <- userSerivce.getRecentOrders(user.id)
  inventory <- inventoryService.getCurrentInventory()
  items = ...
} yield {
  ...
}


Код один и тот же. Но в одном случае он выглядит последовательным, а в другом — нет.

И, если позволите, я отвечу еще на один момент из другой ветки.

> языки, где асинхронность выражена через костыли

Вот исходя из вышеизложенного, она выражена через костыли почти везде! Я не зря выше привел деепричастия в качестве примера. Где они в языках, где асинхронность поддерживается из коробки? Там есть специальная конструкция для "ожидания" завершения действия, которая в естественных языках выражается глаголами ("дождитесь") или наречиями ("после", "затем"). А вот как "естественно" сделать из синхронной операции асинхронную (т.е. породить деепричастие из глагола) в современных языках? А никак. Задача обычно сводится к вызову других глаголов из библиотек (какой-нибудь threadPool.execute). Получаетя асимметрия между созданием асинхронного вычисления (обычные глаголы в ЯП, нет встроенной конструкции, в естественных языках — специальная конструкция) и ожиданием (специальная конструкция в ЯП, обычные глаголы или наречия в естественных языках). Более-менее симметричный пример я могу только из Scala привести:
val x = Future { doSomething() }
x.flatMap { v => ...
}

Здесь Future делает асинхронное вычисление из "глагола" (действия в фигурных скобках). Здесь и создание, и использование — обычные вызовы методов. Специальные конструкции языка не используются.

Хотя вообще специальный синтаксис языка полезен. Но не привязанный к асинхронности, а привязанный к последовательности (ага, монады). Это уже упомянутые for comprehension (scala) и do-notation (haskell). Эта необходимость вызвана тем, что наречие "затем" по-разному работает на микро- и макро-уровнях. На микро-уровне: "Приготовьте пирог, затем подайте его" — операция подразумевает, что результат одного шага доступен на следующем. Это удобно для операций вида "приготовить пирог" — они должны всего-лишь заботиться о том, как передать свой непосредственный результат. К сожалению, на макроуровне у нас есть "Приготовьте пирог, затем сварите суп, затем подавайте все на стол". К моменту подачи на стол у нас доступны результаты всех предыдущих операций (и пирог, и суп) а не только последней (суп). Явно протаскивать контекст через внутренние операции — неудобно и неестественно (зачем нам в операции "сварить суп" этот внешний контекст в виде пирога?). Переписывание for как раз устраняет разницу в семантиках. На микро-уровне (затем/flatMap/bind/then) так и остается на индивидуальном уровне. А на верхнем уровне компилятор правильно протаскивает накапливающийся контекст в последующие операции.

do notation и for comprehension — хороший инструмент, но очень ограниченный. К сожалению, он работает только с последовательным выполнением. Ну и ветвление можно более-менее безболезненно сделать. А вот циклы приходится вручную через какие-нибудь fold или рекурсию расписывать. Вот хорошо бы, чтобы компилятор и управляющие конструкции тоже умел преобразовывать (я готов от return отказаться ради такого, if/while вроде бы нормально сводятся к вызовам методов). С учетом деепричастий, что нибудь вида
for[M] def getTopOffers(user: User): Seq[Item] = {
  val userOrdersOp = par userService.getRecentOrders()
  val inventory = inventoryService.getCurrentInventory()
  val userOrders = userOrdersOp

  val items = orders.flatMap(_.items).filter(item => inventory.getCount(item.id) > 0);
  items
}


Как это работает. У нас есть монада M (параметр — конструктор типа). По-умолчанию любое выражение (в том числе — внутри другого выражения) типа M[T] преобразуется в monad.bind(e, { t: T => ...} (или соответствующий эквивалент для циклов). Любое другое выражение — остается тем, которым есть. Выражения, помеченные par не преобразуются в bind (по сути, par — это деепричастный оборот из существующей операции). Т.е. пример выше эквивалентен (аннотации типов добавлены для ясности)
def getTopOffers[M: Monad](user: User): Seq[Item] = {
  val userOrdersOp: M[Seq[Order]] = userService.getRecentOrders()
  for {
    (inventory: Inventory) <- (inventoryService.getCurrentInventory(): M[Inventory])
    (userOrders: Seq[Order]) <- userOrdersOp
    (items: Seq[Item]) = orders.flatMap(_.items).filter(item => inventory.getCount(item.id) > 0)
  } yield 
    items
}

Оно еще потом рассахаривается (за несколько шагов) в ужасы вида
def getTopOffers[M](user: User)(implicit md: Monad[M]): Seq[Item] = {
  val userOrdersOp: M[Seq[Order]] = userService.getRecentOrders()
  
  md.bind(
    inventoryService.getCurrentInventory(),
    inventory => {
      md.mmap(userOrdersOp),
      userOrders => {
        va items = orders.flatMap(_.items).filter(item => inventory.getCount(item.id) > 0)
        items
      }
    }
  )
}

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

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

P.S. Извините за длинный пост. Часть из него — для меня. Я наконец-то начал понимать, чего именно я хочу от абстракций над монадами .
Re[4]: Никогда не недооценивайте силу по умолчанию
От: Pauel Беларусь http://blogs.rsdn.org/ikemefula
Дата: 31.10.22 08:44
Оценка: 13 (2)
Здравствуйте, netch80, Вы писали:

N>2. Для "сложных и развесистых структур данных" можно почитать книжку Окасаки "Чисто функциональные структуры данных" или посмотреть реализации, например, в Erlang.

N>Там этот вопрос решён для большинства типовых структур (деревья, хэш-таблицы и всё такое). Обновление меняет корневые ссылки и сохраняет основное тело структуры.

Раз уж зашла речь про Окасаки, то стоит таки глянуть предисловие от самого Окасаки. Там сказано, что для многих задач он сам не в курсе эффективных решений на функциональщине, а для большинства задач сходится только ассимптотика, а не реальный перформанс.
Re: Никогда не недооценивайте силу по умолчанию
От: vsb Казахстан  
Дата: 11.09.22 13:10
Оценка: 2 (1) +1
Здравствуйте, Caracrist, Вы писали:

C>Однако, иногда нужны были бы модификаторы. Вопрос такой, какое поведение вы выбрали по умолчанию, а какое с модификатором из следующих категорий?


C>Константность:

C>const / mutable

const. Параметры функций не могут быть mutable вообще. А скорей я бы отказался от отдельного ключевого слова для объявления переменной, переменная объявляется сама при первом упоминании.

C>Асинхронность:

C>sync / async

В JS сделано нормально, ничего бы не стал менять. Если язык не низкоуровневый, скорей всего выбрал бы вариант с зелеными потоками и отказ от async-конструкций в принципе.

C>Виртуальность:

C>virtual / direct

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

C>Передача:

C>ref / value

value, ref в языке не нужен.

C>Видимость:

C>public / private / ...

В интерфейсах всё public, в классах всё private (точней package-private), менять это нельзя.
Отредактировано 11.09.2022 13:24 vsb . Предыдущая версия .
Re[4]: Никогда не недооценивайте силу по умолчанию
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 12.09.22 17:31
Оценка: +2
Здравствуйте, vsb, Вы писали:

vsb>>>const. Параметры функций не могут быть mutable вообще.


N>>Почему? Я не могу например счётчик попыток принять и уменьшать?

N>>(Я понимаю, что скопировать нет проблем, но всё же)

vsb>Потому, что это редкий юз-кейс и путает. К примеру в одном из самых популярных codestyle для JS запрещается менять параметры. Фич должно быть как можно меньше. Если это приводит к тому, что в 1% функций ты создашь лишнюю локальную переменную — ну значит так тому и быть. Зато явно будет видно, что ты её создал, она мутабельная и можно отследить её изменения. А с параметрами можно не отслеживать и даже не смотреть на них, ты в 100% случаев знаешь, что они не меняются.


Ну вот почему-то ты сделал такое разделение. А другой скажет, что параметр функции — такая же локальная переменная, но автоматически присвоившая значение на входе. И я не вижу, чем один вариант существенно обоснованнее другого. Мне тоже _чуть-чуть_ было бы удобнее видеть, что параметры неизменяемы, но я не могу найти этому обоснования и не вижу силы за твоим обоснованием.

N>>Во всём коде функции используется kptm2, в одном месте kmpt2. Удачи в поиске. Особенно в конце спринта с рычащим тимлидом.


vsb>Если это неизменяемая переменная, то второе место не скомпилируется.


Кто из этих двоих переменных "это"?

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


"Что-то сильно не так делаешь" это слишком абстрактный аргумент, чтобы иметь хоть какую-то силу. Нужно, чтобы ситуации не происходило вообще, или в крайне малом количестве случаев.
А учитывая, что объявление переменной ещё и (обычно) должно задавать её тип (кроме случаев, когда ты явно разрешил компилятору автовывод типа) — то за аргументы за автосоздание переменной уходят в область бесконечно малых.

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


По-моему, это не сильно связанные вещи.
The God is real, unless declared integer.
Re[4]: Никогда не недооценивайте силу по умолчанию
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 03.10.22 14:37
Оценка: +1 :)
Здравствуйте, Sinclair, Вы писали:

S>Здравствуйте, netch80, Вы писали:

N>>На чём будет написан рантайм Go, реализующий шедулер горутин, и почему этот язык не является нормальным?
S>Ну это же классический вопрос, с не менее классическим ответом.

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

S>Вопрос как раз в том, как мы ограничиваем возможности программиста выстрелить себе в ногу.


Да. Но когда я писал
Автор: netch80
Дата: 10.04.12
то же самое, реакции были неоднозначными

S>Так что не очень важно, на каком языке будет написан рантайм. Он не будет нормальным ровно потому, что в нём вынужденно будет реализована возможность выстрелить себе в ногу, и её предотвращение будет ответственностью программиста, а не компилятора.


Разница будет в том, насколько легко контролировать запреты.
Если язык в принципе не может запортить структуры памяти, как в Java без спец. модулей для этого, а использование таких модулей легко ловится — это одно. Или как когда все опасные места ограждены словом unsafe, и на него можно тупо выгрепать.
Если нет — это другое. Для C++ такое ловить сложнее на порядки.
The God is real, unless declared integer.
Re[7]: Никогда не недооценивайте силу по умолчанию
От: maxkar  
Дата: 19.09.22 14:16
Оценка: 105 (1)
Здравствуйте, SkyDance, Вы писали:

SD>На самом деле было бы интересно посмотреть на язык, с самого начала заточенный на scheduling методов в глобальном пространстве. Не как горутины/fibers/green threads, в пределах одного компьютера, а — в глобальном распределенном кластере. Попыток дофига (всякие там RPC), но все это делается без изменения семантики, просто к функции добавляется еще один return code "не шмогла", или, еще хуже, timeout.


У меня есть пара примеров . Язык для координации различных REST-сервисов в кластере. Дополнительная цель — вынести кастомизацию поведения из севрисов (feature flags) на уровень координатора.

Первый пример (на уровне оркестратора всей системы):
def makePayment(order):
  val paymentClass = order.vendor == '123' ? 'Reliable' : 'Cheap'
  val paymentId = newUUID()
  val paymentRequest = Payment(
    id = paymentId,
    currencies = ...,
    amount = ...,
    customer = ...,
    paymentClass = paymentClass,
    items = order.items
  )
  order.state = Order.PAYMENT_IN_PROGRESS
  order.payments.push(paymentRequest)
  notify paymentSvc of paymentRequest
  wait paymentRequest in paymentSvc to { p ->
    p.status != Payment.AUTHORIZING
  }
  updatemPayment.status match {
    case Payment.REDIRECT => // redirect to URL...
    case Payment.REJECTED => // handle rejected status,
  }

Обработка платежа заказа. На основе вендора определяется, что нам важнее в обработке заказа (чтобы вынести эти проверки из payment service). Создаем платеж. Добавляем платеж к заказу. Затем уведомляем REST payment service о том, что у нас есть новая entity. Среда обеспечивает надежность (повторные запросы при необходимости, для этого мне нужна идемпотентность — REST). Затем мы ожидаем, пока пройдет "промежуточное" состояние Authorizing (в сервисе могут быть свои повторы, таймауты и прочая внешняя интеграция). Уведомление об изменениях — через очередь событий (event queue). Что-то банальное вроде "payment id = ... changed", остальное можно вытянуть через rest зная id. После обновления выполнение продолжается дальше.

Второй пример. Координация действий в рамках одной entity (на стороне payment service)!:
def cancelPayment(my payment, cancelRequest):
  payment.cancelRequests.push(cancelRequest)
  wait payment in self to { p ->
    p.authorization.state != Auth.AUTHORIZING
  }
  payment.authorization.state match {
    case Auth.REJECTED | Auth.CANCELLING | Auth.CANCELLED => 
      cancelRequest.status = Cancel.IGNORED
    case Auth.ACCEPTED =>
      cancelRequest.status = Cancel.IN_PROGRESS
      payment.status = Payment.CANCELLING
      native callExternalService(payment)
  }

Иллюстрирует ситуацию, когда у нас есть промежуточное состояние, в котором нельзя дальше двигаться. Например, отмена платежа зависит от того, авторизован он или нет. Метод регистрирует новый запрос. Затем ждет, пока у нас появится определенность в состоянии. Затем в зависимости от того, как прошла авторизация, выполняем следующие действия. Если авторизация не прошла — ничего делать не надо, только отметить в базе. Иначе нужно поставить промежуточное состояние и вызвать внешний сервис. Здесь native — это "метод расширения" платформы. Например, ручная реализация повторов и отказоустойчивости для внешнего (вендорского) RPC-сервиса. Все выполнение надежно. Если вдруг сервис, выполняющий код, упал — другая машина может взять и продолжить выполнение (с native есть некоторые сложности, но в целом ничего нерешаемого). В языке можно поддерживать все нормальные конструкции (ветвление, циклы, методы).

Что ещё. Условная "транзакционность" и отсутствие гонок (только в рамках entity, write skew я не планирую решать). Вот у нас есть makePayment. В обычной среде во время выполнения (которое ставит PAYMENT_IN_PROCESS) что-то другое может изменить платеж (например, поставить CANCELLED) и будет гонка. В типичном случае это будет http 500 с исключением OptimisticLockingException (хоть кто-нибудь бы это обрабатывал!). А здесь не будет. Гарантируется, что нет конкуретного доступа в участках между notify/wait/native. Это в реализации достаточно просто. У нас есть safe и unsafe инструкции. Safe не содержит доступа к внешним сервисам (но может манипулировать внутренним состоянием). Во время выполнения мы используем ORM (не люблю их, но для задачи подходит). При native/notify/wait пытаемся сохранить (транзакционно) состояние enitity и стек вычисления. Если получилось — продолжаем unsafe операцию, после нее перечитываем entity. Не получилось — начинаем операцию заново с момента предыдущего сохранения.

Таким образом, у нас есть:

Из языка у меня пока только concept-art Первую среду выполнения (runtime) для опробования идей я оцениваю в 6-9 месяцев полной занятости. Т.е. через это время на "низкоуровневом ассемблере" можно будет писать программы, запускать их и вообще пытаться решать реальные задачи координации (то, что сейчас решается API Gateway). А потом еще несколько лет на плюшки вроде высокоуровневого языка, типизации, просмотрщика и отладчика workflow в браузере. Но вообще мне пока нужны безумцы, которые хотели бы попробовать эту идею в реальных системах. Не хочу заниматься задачей без обратной связи.
Re[6]: Никогда не недооценивайте силу по умолчанию
От: SkyDance Земля  
Дата: 23.09.22 05:01
Оценка: 24 (1)
Отлично написано!
Получил истинное удовольствие, читая вашу заметку. Согласен с большинством утверждений. Особенно с этим:

M>Во многих случаях нужна именно последовательность, не синхронность.


В значительной степени это ровно то, что я и имел в виду. В concurrent programming это называют linearizability. Но для человеческого мозга это понятие слишком сложное, и еще сложнее становится манипулировать чем-то, что нас с самого детства учат делать последовательно: читать текст (программы или книги). Неспроста те самые деепричастия считаются довольно продвинутым элементом писательского мастерства. Читательского, однако, тоже — попробуйте прочитать текст со вложенными деепричастиями в сочетании со сложносочиненными и сложноподчиненными предложениями. Я как вспомню некоторые куски "Униженные и оскорбленные" — реальная тренировка для юношеского парсера. Порой приходилось по три раза перечитывать предложение, чтобы понять его смысл. В каждом конкретном предложении есть linearizability, но нет той самой синхронности. На конкретном примере:

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


Сравниваем с этим:

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


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

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

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

M>Вот так мог бы выглядеть современный язык программирования.


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

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

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

initial() ->
    receive
        {accept, Request} -> accepted(Request);
        stop -> stopped()
    end.

accepted(Request) -> 
    Reply = process(Request),
    reply(Reply).

reply(Reply) ->
    send(Reply),
    initial().

process(Request) ->
    ask_for_payment_data(Request),
    ask_for_inventory_data(Request),
    multi_receive
        {Payment, Inventory} -> intersect(Payment, Inventory).


Это позволяет формализовать логику и значительным образом упростить тестирование (путем генерации тестов а-ля quickcheck/proper, с сохранением возможностей по shrinking'у).

Возможно, пример не самый удачный, но зато чуть менее concept art, и, пожалуй, при некотором упорстве реализуемый на Haskell'е. Но проблемы все те же, о которых вы написали — отсутствие условной транзакционности (более того, я вообще не представляю, как делать транзакционность в распределенных системах, даже на уровне банальных key-value storage — до сих пор прикидываю, есть ли какой-то способ надежно положить bidirectional map в этот самый key-value storage).

M>P.S. Извините за длинный пост. Часть из него — для меня. Я наконец-то начал понимать, чего именно я хочу от абстракций над монадами .


Напомнило вот это

Преподаватель другому говорит:
— "Вот студенты тупые, я им раз объясняю – не понимают, второй раз объясняю – не понимают, третий раз объясняю, уже сам понял, а они не понимают".


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


PS: а вопрос про feature flags надо бы еще раз продумать. Только не для REST сервисов (у них все ж есть удобные ограничения вроде идемпотентности запросов), а в более общем случае. Хотя бы для начала научиться атомарно переключать этот самый feature flag на целом кластере машин. Что-нибудь поумнее чем "флаг переключается ровно в 12:00, время синхронизируем по NTP" (хотя это тоже рабочий вариант, не завязанный на network availability в конкретный момент переключения).
Re[5]: Никогда не недооценивайте силу по умолчанию
От: Sinclair Россия https://github.com/evilguest/
Дата: 26.09.22 17:55
Оценка: 4 (1)
Здравствуйте, x-code, Вы писали:
XC>Да можно, только вопрос в другом — что дает константность по умолчанию по сравнению с изменяемостью по умолчанию?
Массу всяческих гарантий.

Например, вы внутри своего кода можете полагаться на то, что никто не сломает данные, на которые вы ссылаетесь.
Допустим, некто А, пишет примерно вот такой класс (на воображаемом языке):
public class Order
{
  public List<OrderItem> Items {get; init;}
  
  public decimal Total { get; init;}
  public Order(List<OrderItems> items)
  {
    Items = items; 
    Total = items.Select(i=>i.LineTotal).Sum();
  }
}

Тут всё вроде бы ок. Items и Total — readonly.
Затем некто B пишет на основе класса, разработанного A, примерно такую программу:
...
var invoiceAmount = 0m;
List<OrderItems> orderItems;
foreach(var attachment in message.GetAttachments())
{
   orderItems.AddRange(ParseEDIOrder(attachment));
   var o = new Order(ediOrder.OrderLines);
   invoiceAmount += o.Total;
   orderCollection.Add(o);
   orderItems.Clear();
}
...

Затем QA отдел замечает, что происходит какая-то ерунда — в обработку уезжают пустые заказы с ненулевыми Total.
A, назначенный на починку своего класса Order, с ужасом понимает, что полученные им в конструкторе items почему-то изменяются без его ведома.
Ок, давайте попробуем это починить:
public class Order
{
  public List<OrderItem> Items {get; init;}
  
  public decimal Total { get; init;}
  public Order(List<OrderItems> items)
  {
    Items = new List<OrderItems>(items); 
    Total = items.Select(i=>i.LineTotal).Sum();
  }
}

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

Затем некто C добавляет в систему новую фичу — если сумма заказа больше 100, то надо сделать на него скидку 3%. Ну, это же очень просто — давайте добавим к order ещё одну позицию:
...
if (o.Total > 100)
   o.Items.Add(new OrderItem("Volume discount", -o.Total * 3/100);
...

Но увы — почему-то инвойс по-прежнему выписывается на сумму без скидки! QA заводит багу на A: "сумма заказа не совпадает с суммой его позиций"
A снова с ужасом понимает, что отданные им наружу Items почему-то изменяются без его ведома.
Ок, давайте попробуем это починить:
public class Order
{
  public IReadOnlyList<OrderItem> Items {get; init;}
  
  public decimal Total { get; init;}
  public Order(List<OrderItems> items)
  {
    Items = new List<OrderItems>(items); 
    Total = items.Select(i=>i.LineTotal).Sum();
  }
}


Теперь С обнаруживает, что его код обработки заказа перестал компилироваться.
Подсмотрев в код, он видит, что на самом деле в Order хранится List, поэтому пишет вот так:
...
if (o.Total > 100)
   ((List<OrderItem>)o.Items).Add(new OrderItem("Volume discount", -o.Total * 3/100);
...

И код начинает компилироваться. Но багу, закрытую А в предыдущем коммите, открывают заново.
Почесав репу, он делает вот так:

public class Order
{
  private List<OrderItem> _items;
  public IReadOnlyList<OrderItem> Items {get => new List<OrderItem>(_items); }
  
  public decimal Total { get; init;}
  public Order(List<OrderItems> items)
  {
    _items = new List<OrderItems>(items); 
    Total = items.Select(i=>i.LineTotal).Sum();
  }
}

Ну всё, теперь можно спать спокойно. Баг теперь в коде C, потому что его вызов Add ничего в заказ не добавляет. Правда, у нас тут клонирование списков направо и налево...


Например, вы внутри своего кода можете полагаться на то, что переданная вам ссылка на что-то продолжит показывать ровно на те же данные.
Рассмотрим выдуманный пример:
1. Некто, назовём его А, пишет утилиту, которая делает две вещи:
— ставит атрибут "compressed" файлам в заданном списке
— подсчитывает сумму размеров тех из них, которые не входят в список игнорируемых.
Делает А это примерно так (на воображаемом языке):

public static int Main(string[] args)
{
  var mask = args.Length > 1 ? args[1] : "*.*";
  List<string> filePaths = GetFilePathsRecursive(mask);  
  SetAllCompressed(filePaths);                              // I
  List<IgnoreMask> ignoredMasks = LoadMasksFromGitIgnore(); 

  filePaths.Remove(f => ignoredMasks.Any(m=>m.IsMatch(f))); // II
  var totalSize = GetTotalSize(filePaths);
  Console.WriteLine($"Total size of ignored files is {totalSize} bytes");
}
  
public static void SetAllCompressed(List<string> filePaths)
{
  for(var i = 0; i < filePaths.Length; i++)
    SetCompressed(filePaths[i]);
}
...


Погоняв тесты, A убеждается, что всё работает, как часы.

2. Через какое-то время некто B отмечает, что работу метода SetAllCompressed можно оптимизировать — ведь включение компрессии и вычисление длины друг от друга никак не зависят. Давайте запускать включение компрессии в другом потоке!
Сделать это очень легко, и его код становится примерно таким:
public static int Main(string[] args)
{
  var mask = args.Length > 1 ? args[1] : "*.*";
  List<string> filePaths = GetFilePathsRecursive(mask);  
  Task compressTask = SetAllCompressed(filePaths);          // I
  List<IgnoreMask> ignoredMasks = LoadMasksFromGitIgnore(); 

  filePaths.Remove(f => ignoredMasks.Any(m=>m.IsMatch(f))); // II
  var totalSize = GetTotalSize(filePaths);
  Console.WriteLine($"Total size of ignored files is {totalSize} bytes");
  compressTask.Wait();
}

public static Task SetAllCompressed(List<string> filePaths)
{
  return new Task(
    () => 
    {
      for(var i = 0; i < filePaths.Length; i++)
        SetCompressed(filePaths[i]);
    }
  );
}


Что может пойти не так? Взяли отлаженный код, чуть поправили — и вперёд.

Однако затем B замечает, что, во-первых, утилита почему-то сжимает не все файлы. А иногда она вылетает с IndexOutOfBounds — но это редко.
Понятно, что проблема — в несоответствии ожиданий. SetAllCompressed ожидает, что переданный ей список будет неизменным в течение всей её работы; а Main грубо нарушает это ожидание в точке II.
Ок, B пытается решить проблему:
public static Task SetAllCompressed(IReadOnlyList<string> filePaths)
{
  return new Task(
    () => 
    {
      for(var i = 0; i < filePaths.Length; i++)
        SetCompressed(filePaths[i]);
    }
  );
}

Теперь, наверное, код Main перестанет компилироваться, и A будет вынужден его исправить.
Отнюдь — ООП работает не так; SetAllCompressed может потребовать изменяемости от своего параметра, но не может потребовать неизменности.
Что остаётся бедному B? Либо городить синхронизацию (и заставляя программу стать обратно однопоточной), либо опять делать защитную копию:
public static Task SetAllCompressed(IReadOnlyList<string> filePaths)
{
  var localPaths = new List<string(filePaths) // выполняем в вызывающем потоке
  return new Task(
    () => 
    {
      for(var i = 0; i < localPaths.Length; i++)
        SetCompressed(localPaths[i]);
    }
  );
}


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

Вот тут мы и приходим к https://learn.microsoft.com/en-us/dotnet/api/system.collections.immutable.immutablelist-1
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re: Никогда не недооценивайте силу по умолчанию
От: Нomunculus Россия  
Дата: 12.09.22 10:39
Оценка: +1
Здравствуйте, Caracrist, Вы писали:


C>Видимость: всё публичное, если надо спрятать добавляешь private и ко.


хмм, почему? По-моему наоборот по-умолчанию все должно быть спрятано. Заставляет задумываться о том, что видно снаружи.
Re[2]: Никогда не недооценивайте силу по умолчанию
От: sergii.p  
Дата: 12.09.22 13:16
Оценка: -1
Здравствуйте, Нomunculus, Вы писали:

Н>хмм, почему? По-моему наоборот по-умолчанию все должно быть спрятано. Заставляет задумываться о том, что видно снаружи.


по-моему, это надуманное ограничение. В большинстве случаев нужно именно public. Прятать нужно очень редко, когда изменение одного поля меняет другие (или тянет за собой побочку).
Вспоминаю свой опыт на java, когда постоянно промахивался и писал puvlic вместо public. Раздражало. Сейчас пишу на С++ и использую struct. Забыл когда писал public вообще.
Отредактировано 12.09.2022 14:42 sergii.p . Предыдущая версия .
Re[3]: Никогда не недооценивайте силу по умолчанию
От: Нomunculus Россия  
Дата: 12.09.22 13:19
Оценка: +1
Здравствуйте, sergii.p, Вы писали:

SP>по-моему, это надуманное ограничение.


Нет, это ограничение, призванное избавить от лишних ошибок.
Простой пример — член класса типа int. Думаешь это нормально что все подряд смогут его менять, а сам объект класса не будет об этом знать? Ну, это может привести к печальным результатам.
Re[4]: Никогда не недооценивайте силу по умолчанию
От: sergii.p  
Дата: 12.09.22 14:32
Оценка: -1
Здравствуйте, Нomunculus, Вы писали:

Н>Здравствуйте, sergii.p, Вы писали:


SP>>по-моему, это надуманное ограничение.


Н>Нет, это ограничение, призванное избавить от лишних ошибок.

Н>Простой пример — член класса типа int. Думаешь это нормально что все подряд смогут его менять, а сам объект класса не будет об этом знать?

к печальным последствиям приводит изменение как таковое. А будет setMember, или просто member дела не меняет. Это конечно справедливо, когда все поля независимы от других и изменением поля нельзя сломать внутреннее состояние. Но это 95% случаев.
Re[6]: Никогда не недооценивайте силу по умолчанию
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 13.09.22 05:21
Оценка: +1
Здравствуйте, vsb, Вы писали:

N>>Ну вот почему-то ты сделал такое разделение. А другой скажет, что параметр функции — такая же локальная переменная, но автоматически присвоившая значение на входе. И я не вижу, чем один вариант существенно обоснованнее другого. Мне тоже _чуть-чуть_ было бы удобнее видеть, что параметры неизменяемы, но я не могу найти этому обоснования и не вижу силы за твоим обоснованием.


vsb>Все мои предпочтения это исключительно opinionated, как говорится. Я кодирую в своих предпочтениях настолько строгий стиль кодирования, насколько это разумно в рамках синтаксиса языка. Конечно нет ничего сакрального в неизменяемых параметрах. Просто мне так кажется правильней. Изменяемые переменные должны быть нужны редко. Но не настолько редко, чтобы от них отказываться, иначе это уже что-то вроде хаскеля со своей историей, как говорится.


Ну вообще всё семейство ФП. Я на Эрланге много писал. Неизменяемые переменные его стиля это, конечно, красиво. Но иногда они превращаются в жуткий геморрой. То надо передавать 50 изменяемых значений в функцию, рекурсивно... — приходится передавать одну большую мапу со значениями. То простейший цикл уже не напишешь без рекурсии. В тяжёлых случаях коллеги вообще переходили на словарь процесса (в его контексте это грубость, как в C++ кастовать указатели между несвязанными классами и ходить по смещению от них).
Вот честно, больше не хочу такого. Тут согласен, что их надо оставить.

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

vsb> mutable параметры нужны ещё реже и от них уже отказаться можно, т.к. всё же это семантически немного другая сущность


Ну вот насколько это "немного", достаточно ли его для другой политики — ХЗ.

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


Про имена это к Swift — там у параметров разделяются внутренние и внешние имена — и это правильно.

N>>>>Во всём коде функции используется kptm2, в одном месте kmpt2. Удачи в поиске. Особенно в конце спринта с рычащим тимлидом.


vsb>>>Если это неизменяемая переменная, то второе место не скомпилируется.


N>>Кто из этих двоих переменных "это"?


vsb>Немного не то сказал. Мой поинт в том, что неизменяемой переменной ты не будешь на ровном месте присваивать новое значение. Т.е. ты не будешь писать код kptm2 = 1; ...; kmpt2 = 2; print(kmpt2). Ты вернёшься на место объявления kptm2 и напишешь mutable kptm2 = 1; ...; kmpt2 = 2; print(kmpt2) к примеру. И вот этот пример уже выдаст ошибку — mutable переменная не была изменена.


Эээ.
1. А с чего бы это сразу объявлять ошибкой? Ну mutable, ну не изменена.
(На самом деле тут ещё один вопрос, конечно. Имеет смысл по умолчанию делать, что ситуации типа "присвоенное значение не используется" создают ошибки. Как минимум в release-сборке, если использовать это деление. Но есть же автоматическая генерация кода. Надо иметь возможность контекстно-локально отключать такие ошибки.)
2. Но представь себе, что kmpt2 написано дважды вместо kptm2 в разных местах. Это тоже вероятно. Всё, твой контроль по принципу единичности — уже не сработает. Контроль по неиспользуемости присвоенного значения — может сработать, а может и нет — в зависимости от интеллекта компилятора.

Ну и см. ниже про типы.

Опциональную политику такого объявления по первому присвоению я бы понял. Тотальную — нет. По умолчанию — тоже нет. Даже для Хаскеля

vsb>Если эта переменная изначально была mutable и ты опечатался в низу функции и случайно завёл новую переменную, тут ничего не сделать, будет баг, да.


Ну вот да — контроль тут будет, мягко говоря, слабым.

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


Надо смотреть на практику Питона, где эта диверсия по умолчанию.

vsb>Я считаю, что есть большая ценность в том, чтобы убрать визуальный шум от объявления неизменяемой переменной, т.к. этот шум читателю кода ничего не даёт.


Хм.
Раньше я был категорическим сторонником стиля "чем меньше букв, тем лучше". А потом начал активно смещаться в сторону того, что некоторая избыточность крайне полезна. Не, например, begin-end вместо {}, это уже перебор, а, например, "var x: int", а не "var x int", как в Go. Обязательны слова типа var, function (даже если сократить до fn, как сейчас модно), и вообще стиль объявлений Pascal / Go / (самый новый) C++ с auto и ->, чтобы не плодить Most Vexing Parse. Ну и прочее. Набирать не сильно больше, а пользы — вагоны.
Так и тут — если ты напишешь "let kptm2 = foo()", или "kptm2 := foo()", как в Go с его присвоением-с-декларацией, а не "kptm2 = foo()", это будет совершенно минимальная добавка, зато исключит массу проблем.

N>>"Что-то сильно не так делаешь" это слишком абстрактный аргумент, чтобы иметь хоть какую-то силу. Нужно, чтобы ситуации не происходило вообще, или в крайне малом количестве случаев.

N>>А учитывая, что объявление переменной ещё и (обычно) должно задавать её тип (кроме случаев, когда ты явно разрешил компилятору автовывод типа) — то за аргументы за автосоздание переменной уходят в область бесконечно малых.

vsb>Автовывод типа это уже давно стандарт де-факто почти во всех языках и подразумевается сам собой.


Да вот не совсем. Ты смешиваешь два автовывода — автовывод по инициализации и автовывод по всему использованию.
Первый — удел процедурных языков. Второй — группы *ML/Haskell/etc.

Но это уклонение от вопроса, потому что:

vsb>Все типы локальных переменных выводятся и это не опционально.


Ну да, ещё скажи, что ты не можешь сказать "int x" для локальной переменной, только "var x"
Непонятно, что ты имел в виду на самом деле, но сказано безнадёжно коряво. Перефразируй.
The God is real, unless declared integer.
Re[7]: Никогда не недооценивайте силу по умолчанию
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 13.09.22 12:58
Оценка: +1
Здравствуйте, sergii.p, Вы писали:

N>>Где 95% случаев и почему?

SP>потому что тип-произведение предназначен для хранения независимо изменямых полей по определению. Если это не так, значит мы спроектировали тип плохо, или у нас не хватает мощности языка.
SP>Собственно ущербность идеи set методов проявляется уже в том, что каждый может вернуть ошибку инварианта. Люди воспитанные на исключениях скажут, что это не проблема. Но не все такие оптимисты...
SP>Решение мне видится в неизменямости объектов. Но признаю, что это не для всех.

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

Вот, например, задача в текущем проекте. Есть набор наблюдаемых ячеек, с которых надо снимать характеристики и трассировать изменения состояния.
И есть особенность состояния "вот сейчас мы готовы отправлять что-то или нет", и где тот, кому надо отправлять.
Я могу вызвать SetCellTrace(int cell_index, bool onoff), который не может дать сам по себе никакой ошибки инварианта, но если хоть у одной ячейки было true, поднимается соединение с получателем. И могу вызвать SetCollectorAddress(address_t address), который может вызвать дисконнект (если адрес пустой), реконнект, или ничего, если сбор не был включен.
То есть эти два сеттера это не просто сеттеры, они вызывают самостоятельные действия получателя.
А ещё рядом есть SetTraceReference(int cell_index, long reference), который действительно тупо сеттер, но когда вызывается отсылка данных, этот reference извлекается и участвует в формировании данных в отправляемой нотификации. А ещё есть аналогичное для trace level, которое управляет объёмом отправляемого.
И что из них собственно "тип-произведение" в твоём смысле, если у каждого поля данных есть побочный эффект?
Только не надо говорить, что всё это надо перепроектировать с нуля и сделать соответствие типам-суммам, типам-произведениям, и всему такому. Неподъёмно и, скорее всего, ненужно.
The God is real, unless declared integer.
Re[5]: Никогда не недооценивайте силу по умолчанию
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 16.09.22 06:27
Оценка: +1
Здравствуйте, SkyDance, Вы писали:

S>>Если у нас есть async-метод, написанный в терминах await — есть ли способ по нему автоматически породить синхронную версию?


SD>Дело в том, что "синхронности" в реальном мире не существует. Это абстракция, появившаяся в силу ограничений человеческого восприятия (что-то очень быстрое воспринимается мгновенным, а два таких события — последовательным).


Это всё хорошо, но для компьютера непрактично, потому что ему, наоборот, синхронность естественна, а асинхронность требует специального дизайна.
По крайней мере пока у него фоннеймановская архитектура (включая параллельные версии).
The God is real, unless declared integer.
Re[3]: Никогда не недооценивайте силу по умолчанию
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 26.09.22 08:42
Оценка: +1
Здравствуйте, x-code, Вы писали:

S>>Пожалуй, только const по умолчания хорош для всех типов языков.


XC>А почему? Ведь в конечном итоге программирование — это работа с ячейками памяти, которая не только для чтения, но и для записи.

XC>Пересоздавать каждый раз данные, особенно если это сложные и развесистые структуры данных — еще больший оверхед чем virtual.

1. Сказано же — по умолчанию, а не всегда

2. Для "сложных и развесистых структур данных" можно почитать книжку Окасаки "Чисто функциональные структуры данных" или посмотреть реализации, например, в Erlang.
Там этот вопрос решён для большинства типовых структур (деревья, хэш-таблицы и всё такое). Обновление меняет корневые ссылки и сохраняет основное тело структуры.
The God is real, unless declared integer.
Никогда не недооценивайте силу по умолчанию
От: Caracrist https://1pwd.org/
Дата: 11.09.22 12:55
Оценка:
Допустим, вы создавали / выбирали бы сегодня новый язык програмирования. В этом языке есть переменные, структуры данных и функции.
Все как у всех.

Однако, иногда нужны были бы модификаторы. Вопрос такой, какое поведение вы выбрали по умолчанию, а какое с модификатором из следующих категорий?

Константность:
const / mutable

Асинхронность:
sync / async

Виртуальность:
virtual / direct

Передача:
ref / value

Видимость:
public / private / ...


Из моего опыта по умолчанию должно быть примерно так:


Какой язык мне посоветуете?
~~~~~
~lol~~
~~~ Single Password Solution
Re[2]: Никогда не недооценивайте силу по умолчанию
От: Caracrist https://1pwd.org/
Дата: 11.09.22 13:22
Оценка:
Здравствуйте, vsb, Вы писали:

vsb>В интерфейсах всё public, в классах всё private, менять это нельзя.


Это интересный момент. Тоесть, поля только private и доступ снаружи только через интерфейс.
А что, мне нравится
~~~~~
~lol~~
~~~ Single Password Solution
Re: Никогда не недооценивайте силу по умолчанию
От: Shtole  
Дата: 11.09.22 19:05
Оценка:
Здравствуйте, Caracrist, Вы писали:

C>Допустим, вы создавали / выбирали бы сегодня новый язык програмирования. В этом языке есть переменные, структуры данных и функции.

C>Все как у всех.

C>Однако, иногда нужны были бы модификаторы. Вопрос такой, какое поведение вы выбрали по умолчанию, а какое с модификатором из следующих категорий?


C>Константность:

C>
C>const / mutable
C>

C>Асинхронность:
C>
C>sync / async
C>

C>Виртуальность:
C>
C>virtual / direct
C>

C>Передача:
C>
C>ref / value
C>

C>Видимость:
C>
C>public / private / ...
C>


Однобоко рассматривать всё это с позиций безопасности. virtual влечёт оверхед, пусть и небольшой. ref / value зависит от наличия адресной арифметики. И так далее. Пожалуй, только const по умолчания хорош для всех типов языков.
Do you want to develop an app?
Re[3]: Никогда не недооценивайте силу по умолчанию
От: Shtole  
Дата: 12.09.22 10:35
Оценка:
Здравствуйте, netch80, Вы писали:

vsb>> А скорей я бы отказался от отдельного ключевого слова для объявления переменной, переменная объявляется сама при первом упоминании.

N>Ты серьёзно??

Я то же самое хотел спросить, но решил, что чего-то недопонял
Do you want to develop an app?
Re: Никогда не недооценивайте силу по умолчанию
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 12.09.22 10:46
Оценка:
Здравствуйте, Caracrist, Вы писали:

C>Допустим, вы создавали / выбирали бы сегодня новый язык програмирования. В этом языке есть переменные, структуры данных и функции.

C>Все как у всех.

C>Однако, иногда нужны были бы модификаторы. Вопрос такой, какое поведение вы выбрали по умолчанию, а какое с модификатором из следующих категорий?


C>Константность:

C>
C>const / mutable
C>


В идеале — вообще просто разные слова (let/var, например). Вот для чего-то вроде параметров функций — или те же слова, или таки mutable.

C>Асинхронность:

C>
C>sync / async
C>


Крайне сложно выбрать, зависит от домена.

C>Виртуальность:

C>
C>virtual / direct
C>


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

C>Передача:

C>
C>ref / value
C>


Учитывая, что в языках типа Java/C#/Python/etc. реально происходит передача _ссылки_ _по значению_, или вопрос некорректный, или надо его расширить. Чистое по ссылке — это, простите, старый Фортран, где сказав call foo(5) можно было получить, что 5 в памяти стало 6 для всех участников, потому что область константы была модифицирована
А вот для объектов, если не введена явная политика тотальной иммутабельности, передача ссылки по значению это таки нормальный вариант.

C>Видимость:

C>
C>public / private / ...
C>


Умолчание — [обновлено] лучше всего package-internal (если есть пакеты), за ним по полезности private. Только так. Легче потом отдельные элементы "расшарить", чем бороться с тем, что тебя поюзали и без согласования с 20 соседними департаментами ты это не ограничишь.

C>Из моего опыта по умолчанию должно быть примерно так:


C>

Опыт сильно напоминает улучшенный JS. Это всё-таки перекос в одну конкретную сторону.

C>
Какой язык мне посоветуете?
C>

Тот, что подходит под твой домен задач.

Вообще подобный опросник в варианте скрестили ужа с ежом, то есть C с JS и накрыли хаскелем, мягко говоря, смущает
The God is real, unless declared integer.
Отредактировано 13.09.2022 5:22 netch80 . Предыдущая версия . Еще …
Отредактировано 12.09.2022 19:49 netch80 . Предыдущая версия .
Re[3]: Никогда не недооценивайте силу по умолчанию
От: vsb Казахстан  
Дата: 12.09.22 11:33
Оценка:
Здравствуйте, netch80, Вы писали:

vsb>>const. Параметры функций не могут быть mutable вообще.


N>Почему? Я не могу например счётчик попыток принять и уменьшать?

N>(Я понимаю, что скопировать нет проблем, но всё же)

Потому, что это редкий юз-кейс и путает. К примеру в одном из самых популярных codestyle для JS запрещается менять параметры. Фич должно быть как можно меньше. Если это приводит к тому, что в 1% функций ты создашь лишнюю локальную переменную — ну значит так тому и быть. Зато явно будет видно, что ты её создал, она мутабельная и можно отследить её изменения. А с параметрами можно не отслеживать и даже не смотреть на них, ты в 100% случаев знаешь, что они не меняются.

N>Ты серьёзно??


Да.

N>Во всём коде функции используется kptm2, в одном месте kmpt2. Удачи в поиске. Особенно в конце спринта с рычащим тимлидом.


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

Я это не упоминал, но подразумевается, что во вложенном блоке нельзя объявить локальную переменную с тем же именем, что и снаружи. Как раз для того, чтобы исключить такую проблему.
Отредактировано 12.09.2022 11:36 vsb . Предыдущая версия .
Re[4]: Никогда не недооценивайте силу по умолчанию
От: vsb Казахстан  
Дата: 12.09.22 13:41
Оценка:
Здравствуйте, Нomunculus, Вы писали:

SP>>по-моему, это надуманное ограничение.


Н>Нет, это ограничение, призванное избавить от лишних ошибок.

Н>Простой пример — член класса типа int. Думаешь это нормально что все подряд смогут его менять, а сам объект класса не будет об этом знать? Ну, это может привести к печальным результатам.

Один код пишется одним-двумя людьми, которые в курсе про писаные и неписаные соглашения. Другой код пишется тысячью индусов, которым надо закрыть тикет побыстрей. В первом случае private мешает. Во-вторым случае private это необходимая фича, без которой код будет погребен под хаками состояний.
Re[5]: Никогда не недооценивайте силу по умолчанию
От: vsb Казахстан  
Дата: 12.09.22 18:03
Оценка:
Здравствуйте, netch80, Вы писали:

vsb>>Потому, что это редкий юз-кейс и путает. К примеру в одном из самых популярных codestyle для JS запрещается менять параметры. Фич должно быть как можно меньше. Если это приводит к тому, что в 1% функций ты создашь лишнюю локальную переменную — ну значит так тому и быть. Зато явно будет видно, что ты её создал, она мутабельная и можно отследить её изменения. А с параметрами можно не отслеживать и даже не смотреть на них, ты в 100% случаев знаешь, что они не меняются.


N>Ну вот почему-то ты сделал такое разделение. А другой скажет, что параметр функции — такая же локальная переменная, но автоматически присвоившая значение на входе. И я не вижу, чем один вариант существенно обоснованнее другого. Мне тоже _чуть-чуть_ было бы удобнее видеть, что параметры неизменяемы, но я не могу найти этому обоснования и не вижу силы за твоим обоснованием.


Все мои предпочтения это исключительно opinionated, как говорится. Я кодирую в своих предпочтениях настолько строгий стиль кодирования, насколько это разумно в рамках синтаксиса языка. Конечно нет ничего сакрального в неизменяемых параметрах. Просто мне так кажется правильней. Изменяемые переменные должны быть нужны редко. Но не настолько редко, чтобы от них отказываться, иначе это уже что-то вроде хаскеля со своей историей, как говорится. mutable параметры нужны ещё реже и от них уже отказаться можно, т.к. всё же это семантически немного другая сущность и то, что они в большинстве языков программирования тождественны локальным переменным, это просто такая мода. Никаких проблем такой отказ не несёт, просто человек объявит лишнюю переменную. Может даже у этой лишней переменной будет более удачное имя.

N>>>Во всём коде функции используется kptm2, в одном месте kmpt2. Удачи в поиске. Особенно в конце спринта с рычащим тимлидом.


vsb>>Если это неизменяемая переменная, то второе место не скомпилируется.


N>Кто из этих двоих переменных "это"?


Немного не то сказал. Мой поинт в том, что неизменяемой переменной ты не будешь на ровном месте присваивать новое значение. Т.е. ты не будешь писать код kptm2 = 1; ...; kmpt2 = 2; print(kmpt2). Ты вернёшься на место объявления kptm2 и напишешь mutable kptm2 = 1; ...; kmpt2 = 2; print(kmpt2) к примеру. И вот этот пример уже выдаст ошибку — mutable переменная не была изменена.

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

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

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

N>"Что-то сильно не так делаешь" это слишком абстрактный аргумент, чтобы иметь хоть какую-то силу. Нужно, чтобы ситуации не происходило вообще, или в крайне малом количестве случаев.

N>А учитывая, что объявление переменной ещё и (обычно) должно задавать её тип (кроме случаев, когда ты явно разрешил компилятору автовывод типа) — то за аргументы за автосоздание переменной уходят в область бесконечно малых.

Автовывод типа это уже давно стандарт де-факто почти во всех языках и подразумевается сам собой. Все типы локальных переменных выводятся и это не опционально.
Re[5]: Никогда не недооценивайте силу по умолчанию
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 12.09.22 19:48
Оценка:
Здравствуйте, sergii.p, Вы писали:

SP>>>по-моему, это надуманное ограничение.


Н>>Нет, это ограничение, призванное избавить от лишних ошибок.

Н>>Простой пример — член класса типа int. Думаешь это нормально что все подряд смогут его менять, а сам объект класса не будет об этом знать?

SP>к печальным последствиям приводит изменение как таковое. А будет setMember, или просто member дела не меняет.


1. Даже если ограничиться твоим примером — меняет хотя бы потому, что если setMember(), ты его одного можешь зарезать или вставить в него отладочную печать, поставить на нём бряк, переделать в установку нескольких других полей, и всё такое. А если прямое присвоение в member, то сначала тебе надо все такие прямые присвоения переделать в вызов сеттера, и только потом разбираться, что же из них недопустимое. (Property стиля C# решают это же косвенно, но ломая ABI для тех модулей, что уже скомпилировались с прямым доступом. Или там это обошли?)

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

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

Кстати, я таки думаю, что самый правильный стиль из этих таки не в C++ private/protected/public, а в Java/C#, где умолчанием является package-internal. При нём и посторонние не пролезут, и круг своих аккуратно ограничен, но достаточно широк, чтобы не кидаться friendʼами попусту.

SP>Это конечно справедливо, когда все поля независимы от других и изменением поля нельзя сломать внутреннее состояние. Но это 95% случаев.


Где 95% случаев и почему?
Вокруг меня всё-таки 95% случаев это когда класс имеет своё состояние, которое надо защищать даже от случайных диверсий поломки целостности.
The God is real, unless declared integer.
Re[7]: Никогда не недооценивайте силу по умолчанию
От: vsb Казахстан  
Дата: 13.09.22 10:15
Оценка:
Здравствуйте, netch80, Вы писали:

vsb>>Немного не то сказал. Мой поинт в том, что неизменяемой переменной ты не будешь на ровном месте присваивать новое значение. Т.е. ты не будешь писать код kptm2 = 1; ...; kmpt2 = 2; print(kmpt2). Ты вернёшься на место объявления kptm2 и напишешь mutable kptm2 = 1; ...; kmpt2 = 2; print(kmpt2) к примеру. И вот этот пример уже выдаст ошибку — mutable переменная не была изменена.


N>Эээ.

N>1. А с чего бы это сразу объявлять ошибкой? Ну mutable, ну не изменена.
N>(На самом деле тут ещё один вопрос, конечно. Имеет смысл по умолчанию делать, что ситуации типа "присвоенное значение не используется" создают ошибки. Как минимум в release-сборке, если использовать это деление. Но есть же автоматическая генерация кода. Надо иметь возможность контекстно-локально отключать такие ошибки.)

Можно делить на ошибки и предупреждения, но в релизном коде не должно быть ни тех, ни других. Хотя я бы вообще предложил кардинальный подход — не делить на ошибки и предупреждения, а генерировать в отладочном режиме бинарник, даже если встречаются ошибки, которые можно локализовать. Я давным давно писал на жава в эклипсе, вот там такая фича была. И это было очень удобно. Не дописал функцию, и ладно. Если туда управление придёт, то вместо недокомпилированного кода вылетит ошибка. А так — какой-то кусок кода недописан, а ты тестируешь другой кусок и тебе на первый кусок пофиг, пускай генерирует, что может. Есть исключения вроде несбалансированных фигурных скобок, но обычно ошибку локализовать можно. А если немножко включить эвристики и проанализировать отступы, можно и про скобки подумать.

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

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

N>Надо смотреть на практику Питона, где эта диверсия по умолчанию.


Вот, кстати, да. Я на питоне не то, чтобы много писал, но немного писал. И конкретно эта фича мне проблем кажется не доставляла.

N>Так и тут — если ты напишешь "let kptm2 = foo()", или "kptm2 := foo()", как в Go с его присвоением-с-декларацией, а не "kptm2 = foo()", это будет совершенно минимальная добавка, зато исключит массу проблем.


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

vsb>>Автовывод типа это уже давно стандарт де-факто почти во всех языках и подразумевается сам собой.


N>Да вот не совсем. Ты смешиваешь два автовывода — автовывод по инициализации и автовывод по всему использованию.

N>Первый — удел процедурных языков. Второй — группы *ML/Haskell/etc.

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

vsb>>Все типы локальных переменных выводятся и это не опционально.


N>Ну да, ещё скажи, что ты не можешь сказать "int x" для локальной переменной, только "var x"

N>Непонятно, что ты имел в виду на самом деле, но сказано безнадёжно коряво. Перефразируй.

Не очень понял, что не понятно. У нас ведь в гипотетическом языке нет ни var ни int. У нас есть только x = 1 или x = get_int_value(). Иными словами типы всех локальных переменных выводятся автоматически и отказаться от этого нельзя. Максимум — добавить каст в инициализирующее выражение, чтобы привести его к нужному типу.
Re[6]: Никогда не недооценивайте силу по умолчанию
От: sergii.p  
Дата: 13.09.22 11:50
Оценка:
Здравствуйте, netch80, Вы писали:

N>Где 95% случаев и почему?


потому что тип-произведение предназначен для хранения независимо изменямых полей по определению. Если это не так, значит мы спроектировали тип плохо, или у нас не хватает мощности языка.
Собственно ущербность идеи set методов проявляется уже в том, что каждый может вернуть ошибку инварианта. Люди воспитанные на исключениях скажут, что это не проблема. Но не все такие оптимисты...
Решение мне видится в неизменямости объектов. Но признаю, что это не для всех.
Re: Никогда не недооценивайте силу по умолчанию
От: mrTwister Россия  
Дата: 13.09.22 16:00
Оценка:
Здравствуйте, Caracrist, Вы писали:

C>

Это вообще ни к селу. В нормальных языках есть горутины или аналог и про синхронность или асинхронность думать вообще не надо.
лэт ми спик фром май харт
Re[2]: Никогда не недооценивайте силу по умолчанию
От: SkyDance Земля  
Дата: 13.09.22 23:32
Оценка:
T>Это вообще ни к селу. В нормальных языках есть горутины или аналог и про синхронность или асинхронность думать вообще не надо.

Как по мне так наоборот. Собственно, асинхронность (или concurrency в общем виде) как first class citizen и есть ключевое свойство современных языков. Я бы даже жестче выразился — языки, где асинхронность выражена через костыли (привет, С++ и Java), а не встроена в синтаксис языка (Hello Joe), — устарели лет уже 10 как.

Проблема как раз в том, что про синхронность или асинхронность нужно очень много думать — см. вопросы про примитивы синхронизации на собеседованиях.
Re[6]: Никогда не недооценивайте силу по умолчанию
От: Sinclair Россия https://github.com/evilguest/
Дата: 14.09.22 04:34
Оценка:
Здравствуйте, netch80, Вы писали:

N>(Property стиля C# решают это же косвенно, но ломая ABI для тех модулей, что уже скомпилировались с прямым доступом. Или там это обошли?)

Нет, не обошли. В шарпе (и дотнете вообще) есть очень немного способов подменить реализацию зависимости без перекомпиляции зависимого.
Правила вычисления этих способов настолько нетривиальны, что стандартной является рекомендация "Всегда перекомпилируйте A при перекомпиляции B, если A хоть что-то импортирует из B".
Начиная с public const, которые просто вхардкоживаются в использующий код, и заканчивая всякими disambiguation rules.

N>2. А ещё важнее то, что если у тебя в публичном доступе есть только методы, сохраняющие инварианты, а прямая установка поля этот инвариант может сломать, то выставление в public только беспроблемных методов реально повышает защиту.

+10.
N>Да, это азы, и я надеюсь, что объяснять их не надо было, но это необходимо как вводная к тому, что чем меньше автоматического public, тем лучше для сохранности.
Вообще, по моему опыту, как раз есть исчезающе мало случаев, когда состояние объекта можно менять без его ведома. Всегда есть какие-то неожиданные инварианты — кроме примитивных value-типов.
Ну, там, какой-нибудь "класс" Point с целыми X и Y.

N>Кстати, я таки думаю, что самый правильный стиль из этих таки не в C++ private/protected/public, а в Java/C#, где умолчанием является package-internal. При нём и посторонние не пролезут, и круг своих аккуратно ограничен, но достаточно широк, чтобы не кидаться friendʼами попусту.

+
N>Где 95% случаев и почему?
N>Вокруг меня всё-таки 95% случаев это когда класс имеет своё состояние, которое надо защищать даже от случайных диверсий поломки целостности.
Подозреваю, что речь идёт о какой-нибудь примитивной математике над структурами и массивами.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[3]: Никогда не недооценивайте силу по умолчанию
От: Sinclair Россия https://github.com/evilguest/
Дата: 14.09.22 04:39
Оценка:
Здравствуйте, SkyDance, Вы писали:
SD>Проблема как раз в том, что про синхронность или асинхронность нужно очень много думать — см. вопросы про примитивы синхронизации на собеседованиях.
Вот это — интересная концепция.
Если у нас есть async-метод, написанный в терминах await — есть ли способ по нему автоматически породить синхронную версию?

Сходу мне это кажется сомнительным с практической точки зрения.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[4]: Никогда не недооценивайте силу по умолчанию
От: SkyDance Земля  
Дата: 15.09.22 16:55
Оценка:
S>Если у нас есть async-метод, написанный в терминах await — есть ли способ по нему автоматически породить синхронную версию?

Дело в том, что "синхронности" в реальном мире не существует. Это абстракция, появившаяся в силу ограничений человеческого восприятия (что-то очень быстрое воспринимается мгновенным, а два таких события — последовательным).

Синхронный вызов есть ни что иное как жесткая связка call + return. Стало быть, два асинхронных сообщения: первое — call, второе — return. В некоторых реализациях (называемых directly threaded) вторая инструкция, return, называется continue, и адрес, с которого следует продолжить исполнение — continuation pointer.

Как только мозг осознает данную концепцию, так сразу становится легко понять, почему все методы всегда по природе своей асинхронны. Синхронность нужна лишь для того, чтобы упростить логическое доказательство корректности (что называется, easier to reason about). По сути это запрет на любые действия между отправкой сообщения "call" и вечным (то есть без таймаута) ожиданием ответа (return).
Re[8]: Никогда не недооценивайте силу по умолчанию
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 16.09.22 06:02
Оценка:
Здравствуйте, vsb, Вы писали:

vsb>Можно делить на ошибки и предупреждения, но в релизном коде не должно быть ни тех, ни других. Хотя я бы вообще предложил кардинальный подход — не делить на ошибки и предупреждения, а генерировать в отладочном режиме бинарник, даже если встречаются ошибки, которые можно локализовать. Я давным давно писал на жава в эклипсе, вот там такая фича была. И это было очень удобно. Не дописал функцию, и ладно. Если туда управление придёт, то вместо недокомпилированного кода вылетит ошибка.


Прикольно. Для этого требовался особый JDK, или Eclipse внутри себя это делал?

vsb>Что касается автоматической генерации кода, на мой взгляд правильно требовать от генератора генерировать нормальный код, а не подстраивать язык под него.


Вот в том, что такое "нормальный" код, тут и есть засада. Go не пропускает, например, код с неприменённым импортом. Генератор должен следить за тем, вызвал он что-то по этому импорту или нет? Я думаю, что не должен. Даже если по умолчанию это полезно (Go рассчитывался на написание кода низкоуровневыми ширнармассами, и жёсткий контроль имеет смысл), опция компиляции для убирания этого (вписанная в исходник) была бы очень полезна.

N>>Надо смотреть на практику Питона, где эта диверсия по умолчанию.


vsb>Вот, кстати, да. Я на питоне не то, чтобы много писал, но немного писал. И конкретно эта фича мне проблем кажется не доставляла.


А мне доставляла — и нетипизированностью, и тем, что сложно понимать, определена переменная в принципе или нет.

vsb>>>Автовывод типа это уже давно стандарт де-факто почти во всех языках и подразумевается сам собой.


N>>Да вот не совсем. Ты смешиваешь два автовывода — автовывод по инициализации и автовывод по всему использованию.

N>>Первый — удел процедурных языков. Второй — группы *ML/Haskell/etc.

vsb>Я про первый. Автовывод типов для функций — вопрос сложный.


Не обязательно для функций.

r = 0;
... какие-то действия в цикле... {
++r;
}

Если оно умеет подсчитать, что r достигает 2000, и 1 байта мало, нужно два — это и есть второй вариант.

vsb>>>Все типы локальных переменных выводятся и это не опционально.


N>>Ну да, ещё скажи, что ты не можешь сказать "int x" для локальной переменной, только "var x"

N>>Непонятно, что ты имел в виду на самом деле, но сказано безнадёжно коряво. Перефразируй.

vsb>Не очень понял, что не понятно. У нас ведь в гипотетическом языке нет ни var ни int. У нас есть только x = 1 или x = get_int_value(). Иными словами типы всех локальных переменных выводятся автоматически и отказаться от этого нельзя. Максимум — добавить каст в инициализирующее выражение, чтобы привести его к нужному типу.


Значит, таки типы есть, раз касты есть. Но зачем тогда запрещать тип для переменной?
The God is real, unless declared integer.
Re[2]: Никогда не недооценивайте силу по умолчанию
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 16.09.22 06:05
Оценка:
Здравствуйте, mrTwister, Вы писали:

C>>

T>Это вообще ни к селу. В нормальных языках есть горутины или аналог и про синхронность или асинхронность думать вообще не надо.


На чём будет написан рантайм Go, реализующий шедулер горутин, и почему этот язык не является нормальным?
The God is real, unless declared integer.
Re[9]: Никогда не недооценивайте силу по умолчанию
От: vsb Казахстан  
Дата: 16.09.22 06:24
Оценка:
Здравствуйте, netch80, Вы писали:

vsb>>Можно делить на ошибки и предупреждения, но в релизном коде не должно быть ни тех, ни других. Хотя я бы вообще предложил кардинальный подход — не делить на ошибки и предупреждения, а генерировать в отладочном режиме бинарник, даже если встречаются ошибки, которые можно локализовать. Я давным давно писал на жава в эклипсе, вот там такая фича была. И это было очень удобно. Не дописал функцию, и ладно. Если туда управление придёт, то вместо недокомпилированного кода вылетит ошибка.


N>Прикольно. Для этого требовался особый JDK, или Eclipse внутри себя это делал?


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


vsb>>Что касается автоматической генерации кода, на мой взгляд правильно требовать от генератора генерировать нормальный код, а не подстраивать язык под него.


N>Вот в том, что такое "нормальный" код, тут и есть засада. Go не пропускает, например, код с неприменённым импортом. Генератор должен следить за тем, вызвал он что-то по этому импорту или нет? Я думаю, что не должен. Даже если по умолчанию это полезно (Go рассчитывался на написание кода низкоуровневыми ширнармассами, и жёсткий контроль имеет смысл), опция компиляции для убирания этого (вписанная в исходник) была бы очень полезна.


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


N>Не обязательно для функций.


N>r = 0;

N>... какие-то действия в цикле... {
N> ++r;
N>}

N>Если оно умеет подсчитать, что r достигает 2000, и 1 байта мало, нужно два — это и есть второй вариант.


Это интересный вопрос, что делать с int-ами. В целом я бы хотел, чтобы по умолчанию оно работало автоматически, сначала 4 байта (по-моему меньше — смысла нет), если переполнились, то автоматически превращало в 8, потом в безразмерное целое. Но надо подумать над этим вопросом. Понятно, что если язык претендует на хоть какую-то производительность, такое может быть не реализуемо.

vsb>>>>Все типы локальных переменных выводятся и это не опционально.


N>>>Ну да, ещё скажи, что ты не можешь сказать "int x" для локальной переменной, только "var x"

N>>>Непонятно, что ты имел в виду на самом деле, но сказано безнадёжно коряво. Перефразируй.

vsb>>Не очень понял, что не понятно. У нас ведь в гипотетическом языке нет ни var ни int. У нас есть только x = 1 или x = get_int_value(). Иными словами типы всех локальных переменных выводятся автоматически и отказаться от этого нельзя. Максимум — добавить каст в инициализирующее выражение, чтобы привести его к нужному типу.


N>Значит, таки типы есть, раз касты есть. Но зачем тогда запрещать тип для переменной?


Типы есть, в том числе типы по текущей концепции обязательны у функций. Зачем запрещать? Потому, что два способа написать одно и то же это плохо. И когда нет формального способа решать, какой способ лучше, это плохо. Любая отсылка к какому-нибудь эстетическому чувству программиста, мол он сам выберет — ставить тип или нет, это плохо, это ненадёжно. Можно считать эту концепцию антиподом философии Perl ("всегда есть более одного способа написать это").

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

Если вернуться к int-ам в этой концепции, то можно добавить для них возможность указания нестандартного размера. К примеру по умолчанию int32, но если это умолчание не подходит, то можно указать другой размер (при этом int32 указать нельзя). Хотя я бы предпочёл для этого юз-кейса синтаксис вроде x = (int8) 0, от кастов-то всяко никуда не деться.
Отредактировано 16.09.2022 6:29 vsb . Предыдущая версия .
Re[4]: Никогда не недооценивайте силу по умолчанию
От: Ночной Смотрящий Россия  
Дата: 16.09.22 18:02
Оценка:
Здравствуйте, Sinclair, Вы писали:

S>Если у нас есть async-метод, написанный в терминах await — есть ли способ по нему автоматически породить синхронную версию?


Только GetAwaiter().GetResult(), что смысла особого не имеет. А по честному выпрямить когда у тебя все ранво все упрется в библиотечные методы, возвращающие Task или overlapped IO — без шансов.
... << RSDN@Home 1.3.17 alpha 5 rev. 62>>
Re[6]: Никогда не недооценивайте силу по умолчанию
От: SkyDance Земля  
Дата: 16.09.22 18:26
Оценка:
N>По крайней мере пока у него фоннеймановская архитектура (включая параллельные версии).

Только если рассуждать в пределах одного ядра вычислений. Если взять абстракции более высокого уровня (distributed computing), то уже не все так очевидно.

На самом деле было бы интересно посмотреть на язык, с самого начала заточенный на scheduling методов в глобальном пространстве. Не как горутины/fibers/green threads, в пределах одного компьютера, а — в глобальном распределенном кластере. Попыток дофига (всякие там RPC), но все это делается без изменения семантики, просто к функции добавляется еще один return code "не шмогла", или, еще хуже, timeout.
Re[5]: Никогда не недооценивайте силу по умолчанию
От: Sinclair Россия https://github.com/evilguest/
Дата: 17.09.22 04:37
Оценка:
Здравствуйте, Ночной Смотрящий, Вы писали:
НС>Только GetAwaiter().GetResult(), что смысла особого не имеет. А по честному выпрямить когда у тебя все ранво все упрется в библиотечные методы, возвращающие Task или overlapped IO — без шансов.
То есть только с помощью словаря, который сопоставляет асинк методам их синхронную версию.
В дотнете это, наверное, бессмысленно — синк и асинк слишком по-разному работают.
К примеру, синхронную версию платформенного апи можно вызывать со Span<byte>, что офигенно эффективно и во многих сценариях позволяет сократить вмешательство GC.
А в асинхронной версии так сделать нельзя — надо тащить полноценные Memory<T> или ещё какие-то аналоги, живущие в хипе.
То есть даже если мы автоматически породим синхронную версию кода, и заменим вызовы overlapped io на синхронные, то общее решение будет далеко от идеала.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[2]: Никогда не недооценивайте силу по умолчанию
От: x-code  
Дата: 26.09.22 05:33
Оценка:
Здравствуйте, Shtole, Вы писали:

S>Пожалуй, только const по умолчания хорош для всех типов языков.


А почему? Ведь в конечном итоге программирование — это работа с ячейками памяти, которая не только для чтения, но и для записи.
Пересоздавать каждый раз данные, особенно если это сложные и развесистые структуры данных — еще больший оверхед чем virtual.
Re[4]: Никогда не недооценивайте силу по умолчанию
От: x-code  
Дата: 26.09.22 09:49
Оценка:
Здравствуйте, netch80, Вы писали:

N>1. Сказано же — по умолчанию, а не всегда

N>2. Для "сложных и развесистых структур данных" можно почитать книжку Окасаки "Чисто функциональные структуры данных" или посмотреть реализации, например, в Erlang.
N>Там этот вопрос решён для большинства типовых структур (деревья, хэш-таблицы и всё такое). Обновление меняет корневые ссылки и сохраняет основное тело структуры.

Да можно, только вопрос в другом — что дает константность по умолчанию по сравнению с изменяемостью по умолчанию?
Re[3]: Никогда не недооценивайте силу по умолчанию
От: Sinclair Россия https://github.com/evilguest/
Дата: 03.10.22 06:03
Оценка:
Здравствуйте, netch80, Вы писали:
N>На чём будет написан рантайм Go, реализующий шедулер горутин, и почему этот язык не является нормальным?
Ну это же классический вопрос, с не менее классическим ответом.
С точки зрения тьюринг-полноты более-менее все современные языки эквивалентны.
Вопрос как раз в том, как мы ограничиваем возможности программиста выстрелить себе в ногу.
Пока что всё выглядит так, что мы берём небезопасный язык X, и на нём реализуем некоторый искусственно ограниченный язык Y, в котором выстрелить себе в ногу значительно сложнее, чем в X.
Вот, например, java. В ней вызвать разрушение памяти значительно сложнее, чем в любом нативном языке вроде C, C++, или Паскаля.
Зато вот многопоточность в ней — рудиментарная. Написать на ней некорректную многопоточную программу всё ещё значительно проще, чем корректную.
Поэтому прогрессивное человечество ищет нотации, альтернативные навязшим в зубах fork/join и thread.start() / thread.interrupt().
Горутины, Cω, async методы из C# и javascript — всё это попытки решить эту задачу.

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

Точно так же, как рантайм type-safe java написан на type-unsafe C++. Это не делает С++ type-safe языком.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[7]: Никогда не недооценивайте силу по умолчанию
От: maxkar  
Дата: 16.10.22 12:28
Оценка:
Здравствуйте, SkyDance, Вы писали:

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

Согласен. Второй текст гораздо проще читается.

M>>Вот так мог бы выглядеть современный язык программирования.

SD>Это единственное утверждение, с которым я позволю себе не согласиться.
SD>Если же оставаться в рамках существующих ограничений (программа — текст, читаем сверху вниз, слева направо), то я бы скорее видел это как машину состояний в примерно таком стиле:
Спасибо, интересное наблюдение. Особенно с учетом того, что я специально хотел отойти от явного описания конечных автоматов . Но дело здесь скорее в том, что я пытаюсь решать немного другую задачу. В первую очередь — избежать комбинаторного взрыва состояний там, где он возможен. В рамках вашего примера это
process(Request) ->
    ask_for_payment_data(Request),
    ask_for_inventory_data(Request),
    multi_receive
        {Payment, Inventory} -> intersect(Payment, Inventory).

Смотрите. Есть "пассивные" состояния, в которых мы ждем какого-либо внешнего события (например, initial в вашем примере). Вот описание этих состояний и переходов между ними прекрасно ложится в машину состояний. Или, например, в UML State Diagram. А еще есть "активные" состояния (вроде process), где система что-то делает внутри. Переход в другие состояния обычно "внутренний", не на основании пользовательских запросов. Вот как раз process в этом сценарии показателен. Его можно представить в виде конечного автомата. Это будет суперпозиция состояний для payment и inventory. Т.е. (payment_complete, inventory_in_progress). В этом случае будут классические реации на "внешние" события типа "payment complete". Но количество явных состояний растет очень быстро. А еще ведь можно добавить внешние запросы. Например, пришел cancel и по завершению текущей активности нужно бы его обработать. И тогда у нас уже состояние вроде (payment_in_progress, inventory_failed, cancel_requested). Когда payment будет завершен, его нужно будет откатить. Получается, что для описания активностей (activity diagram в UML) нужны немного другие средства, чем для описания высокоуровневых состояний.

Далее. У вас есть multi_receive. Проблема в том, что это может быть несколько непрактично. Например, у платежа (в рамках payment) может быть много разных состояний. Например, new -> authenticating -> (optional) SCA -> successful/failed. Состояния предоставляются API. Для какой-нибудь службы поддержки видимость состояния SCA (strong customer authentication) может быть полезена. Отсюда у меня в примере и берется wait. Т.е. вместо receive(Payment) получается wait payment to { p => hasDecision(p) }. Мы ждем не первого события, а некоторого состояния, в котором находится платеж (наблюдение состояний реализуется через те же события). Чем еще хорошо ожидание — оно развязывает нас от потенциальных гонок с событиями. Вдруг соответствующее событие уже произошло? В случае wait семантика не меняется. Да и на REST это ложится — можно запросить состояние в момент wait и проверить предикат.

В целом, в рамках process мы делаем примерно одно и то же. Ну, с точностью до терминов. А так — я согласен, что на уровне выше машина состояний как раз была бы полезна. У меня как раз не было продумано, что вообще делать со внешними запросами к системе. Я фокусировался на activity. С состояниями вроде бы получается хорошо. В "пассивных" состояниях у нас описыавется логика реакции на запросы ("события"). В активных — описывается логика поведения но не запросов (внешние запросы слишком усложняют обработку). Зато запросы можно сохранять (HTTP 202 Accepted) до того момента, когда система перейдет в "пассивное" состояние и там уже запрос можно будет обработать. Переходы и события между состояниями не обязательно описывать текстом. Вот как раз state diagram отлично подходит. Стрелочки с guard — раз реакция на внешние события. И для определенных состояний может уже задаваться логика поведения. Это может быть "последовательный" код. Может быть — activity diagram (но в сложных случаях код все-таки оказывается проще для восприятия). Может даже вложенная state diagram.

SD>Возможно, пример не самый удачный, но зато чуть менее concept art, и, пожалуй, при некотором упорстве реализуемый на Haskell'е.

Пример хороший. Он вполне позволяет иллюстрировать те проблемы, которые я пытался решить.

SD>Но проблемы все те же, о которых вы написали — отсутствие условной транзакционности (более того, я вообще не представляю, как делать транзакционность в распределенных системах, даже на уровне банальных key-value storage — до сих пор прикидываю, есть ли какой-то способ надежно положить bidirectional map в этот самый key-value storage).

А вот в key-value транзакционность может внезапно оказаться сложнее, чем для реальных задач. Практически вся интеграция имеет понятие aggregation root. Например, в рамках платежной подсистемы у нас на верхнем уровне будет payment, к нему все будет привязано. В рамках checkout — будет order, включающий cсылки на payment. В этом случае в рамках системы можно денормализованно хранить весь объект. На границах между системами у нас будет двухфазный коммит и/или do+undo. Каждая интеграция со внешней системой в случае завершения операции будет возвращать handler вида:
trait TxHandler {
  def onCommit(): Unit;
  def onRollback(): Unit;
}

В условиях идемпотентности вызова и commit/rollback (rollback может быть undo) мы можем сохранять все состояния вычисления (в том числе TxHandlers) и повторять/вызывать их при необходимости. Поэтому у нас будет Atomic (мы либо все докатим, либо откатим). И будет eventual consistency. Совершенно не будет изоляции (видны все эти переходы). И будет durability.

Что же касается bidi map — да, сложно. В том числе — из-за семантики. Что делать в случае дублирования? С другой стороны, это можно реализовать в тех же механизмах, что и rest. Допустим, у нас прямое отображение считается главным. Тогда reverse map — подчиненное. Когда мы пишем "key->value", нужно предусмотреть механизмы для "надежной доставки". Например, "reverse updated". Тогда будет двухфазный коммит. Мы записали "key:value,revDelivered:false,v:3". Затем обновили "value:key,keyVersion:3" и записали "key:value,revDelivered:false,v:3". Для конкретных хранилищ может быть что-нибудь попроще. Например, в mongo можно подписаться на changesteram и отслеживать изменения "пачками".

SD>На самом деле, думаю, если ваши наблюдения оформить в виде чуть более упорядоченного текста, получился бы неплохой блог-пост (если, конечно, такие вещи вас интересуют).

Спасибо! Блоги немного интересуют. Но кто их читать будет? Поэтому я уж лучше здесь, в рамках темы.
Re[3]: Никогда не недооценивайте силу по умолчанию
От: ути-пути Россия  
Дата: 17.10.22 19:49
Оценка:
Здравствуйте, netch80, Вы писали:

N>Во всём коде функции используется kptm2, в одном месте kmpt2. Удачи в поиске. Особенно в конце спринта с рычащим тимлидом.


Тебе, скорее всего, это еще IDE подсветит. Или, позже, транслятор ругнется предупреждением. А если имя не такое страшное, как ты привел, то, скорее всего, длинное, и набирать ты его не будешь, а выберешь из комплита.
Переубедить Вас, к сожалению, мне не удастся, поэтому сразу перейду к оскорблениям.
Re[8]: Никогда не недооценивайте силу по умолчанию
От: SkyDance Земля  
Дата: 18.10.22 03:19
Оценка:
M>Спасибо, интересное наблюдение. Особенно с учетом того, что я специально хотел отойти от явного описания конечных автоматов . Но дело здесь скорее в том, что я пытаюсь решать немного другую задачу. В первую очередь — избежать комбинаторного взрыва состояний там, где он возможен.

Ага, теперь понимаю. Да, тоже с этой проблемой мучаюсь. Теоретически можно разделить state и attributes (те, которые есть во всех состояниях). Тем самым потенциально можно слепить три состояния "жду ответа от payments и inventory", "жду ответа от payments", "жду ответа от inventory" в одно состояние "жду ответов" (и атрибутами задать, что именно за ответов жду).

M>Далее. У вас есть multi_receive. Проблема в том, что это может быть несколько непрактично. Например, у платежа (в рамках payment) может быть много разных состояний. Например, new -> authenticating -> (optional) SCA -> successful/failed.


В таком варианте это уже два конечных автомата (что, кстати, великолепно ложиться на систему акторов). Хорошо если взаимодействие между ними тривиально. Но если нет, то взрывной рост сложности, увы, гарантирован.

M> В "пассивных" состояниях у нас описыавется логика реакции на запросы ("события"). В активных — описывается логика поведения но не запросов (внешние запросы слишком усложняют обработку).


Хм, я перечитал примеры исходного кода в вашем предыдущем сообщении, но не смог разглядеть это за синтаксисом. Более того, некоторые вещи в синтаксисе мне кажутся слишком узкоспециализированными. Скажем, вот это:
notify paymentSvc of paymentRequest


Почему не "send paymentRequest to PaymentSvc"?

Аналогично, следует ли разделять запрос и ответ семантически? Т.е. что должен делать paymentSvc, тоже писать "notify <???> of paymentReply"? Или как-то иначе, типа "reply paymentReply to <???>"

M>А вот в key-value транзакционность может внезапно оказаться сложнее, чем для реальных задач. Практически вся интеграция имеет понятие aggregation root. Например, в рамках платежной подсистемы у нас на верхнем уровне будет payment, к нему все будет привязано. В рамках checkout — будет order, включающий cсылки на payment. В этом случае в рамках системы можно денормализованно хранить весь объект. На границах между системами у нас будет двухфазный коммит и/или do+undo.


Ага, это сравнительно свежий подход к задаче консенсуса (всякие вариации Merkle tree и blockchain, заодно решающая проблему верификации и восстановления). Но производительность такого решения может сделать бессмысленным само применение key-value, ибо реляционная БД окажется быстрее.

M> Например, в mongo можно подписаться на changesteram и отслеживать изменения "пачками".


Тоже классика распределенного консенсуса. Те самые learners, которые не участвуют в кворуме (не голосуют за принятие решения и не обеспечивают durability), но в курсе текущего состояния. Беда в том, что consistency таких решений уж очень eventual, и с гарантиями совсем беда. Я по этим граблям уже не первый год хожу
В конечном итоге мне очень-очень-очень хочется продвинуть идею заменить эти bidi maps на что-нибудь с лУчшими гарантиями. Что — пока не знаю. Хорошо бы выслушать кого-нибудь, кто уже аналогичную проблему решил.
Re[4]: Никогда не недооценивайте силу по умолчанию
От: Privalov  
Дата: 18.10.22 12:04
Оценка:
Здравствуйте, ути-пути, Вы писали:

УП>Тебе, скорее всего, это еще IDE подсветит. Или, позже, транслятор ругнется предупреждением. А если имя не такое страшное, как ты привел, то, скорее всего, длинное, и набирать ты его не будешь, а выберешь из комплита.


Конкретно в Фортране я не видел подсветок. Использовал Programmer's WorkBench, Fortran Power Station 1.0 и 4.0. Это объяснимо: в Фортране зарезервированных слов нет. И компилятор Фортрана когда-то распознавал только 6 символов имени. Потом это починили, но математики как писали FR3M2, так и пишут. При этом они потом такие имена свободно читают.
Re[5]: Никогда не недооценивайте силу по умолчанию
От: ути-пути Россия  
Дата: 18.10.22 12:17
Оценка:
Здравствуйте, Privalov, Вы писали:

P>Конкретно в Фортране я не видел подсветок. Использовал Programmer's WorkBench, Fortran Power Station 1.0 и 4.0. Это объяснимо: в Фортране зарезервированных слов нет. И компилятор Фортрана когда-то распознавал только 6 символов имени. Потом это починили, но математики как писали FR3M2, так и пишут. При этом они потом такие имена свободно читают.


Ну вот тут обещают подсветку, не знаю, насколько продвинутую, но тем не менее.
Но имхо, ужасное фортрановское имя было лишь примером, вряд ли тут многие на нем пишут.
Переубедить Вас, к сожалению, мне не удастся, поэтому сразу перейду к оскорблениям.
Re[6]: Никогда не недооценивайте силу по умолчанию
От: Privalov  
Дата: 18.10.22 12:32
Оценка:
Здравствуйте, ути-пути, Вы писали:

УП>Ну вот тут обещают подсветку, не знаю, насколько продвинутую, но тем не менее.


Значит, анализируют так же, как компилятор. Я помню, раньше материала "как не надо писать на Фортране" было намного больше, чем "как надо писать на Фортране". Ну и может быть в последних стандартах что-то доработали. Я имел дело с версиями Фортрана до 90. Я даже свободный формат читаю с трудом.

УП>Но имхо, ужасное фортрановское имя было лишь примером, вряд ли тут многие на нем пишут.


Математики такие имена используют постоянно. И в них есть смысл. Но да, мне, как и netch80, приходилось возиться с путаницей в именах. Компилятор не предупреждал. Молча создавал переменную по умолчанию. Я когда про implicit none узнал, чуть танцы солнцепоклонников прямо перед компом не устроил. Правда, команда математиков (они все были дядьки заметно старше меня) на него забили. Видимо, привыкли к первой букве.
Re[9]: Никогда не недооценивайте силу по умолчанию
От: maxkar  
Дата: 23.10.22 10:26
Оценка:
Здравствуйте, SkyDance, Вы писали:

SD>
SD>notify paymentSvc of paymentRequest
SD>

SD>Почему не "send paymentRequest to PaymentSvc"?
А это потому, что я рассказываю модель вычисления с середины а не с самого начала . Если же начинать с самого начала, получается следующее.

Давайте для начала рассмотрим одну систему (сервис). У нее есть какое-то внутреннее состояние и есть какие-то представления этого состояния, который сервис может отдавать наружу. Запросы к сервису могут возвращать состояние, а могут приводить к изменению состояния. Дополнительное условие — любой "модифицирующий" запрос потенциально изменяет какое-то обозримое состояние. Не бывает так, чтобы что-то внутри изменилось, но наружу это ни в каком виде видно не было. Какой смысл тогда в запросах? При этом необязательно, чтобы всё состояние было доступно клиентам. Например, какие-то изменения могут быть доступны только через "системные" API (которые при этом используются в тестах). В целом всё API описывается доступными состояниями и правилами переходов: что может происходить "самопроизвольно" (по правилам системы) и что происходит по запросам клиентов.

Теперь добавим другие системы и посмотрим на характер запросов между ними. Можно сказать, что это либо запросы на чтение состояний (ничего не изменяющие), либо на изменение состояний. Других запросов вроде бы не нужно — зачем что-то посылать, если в результате ничего не изменится? Запросы у нас имеют не стандартную операционную форму "пожалуйста, сделай XXX" а "я считаю, что представление состояния сущности XXX должно быть YYY". Например, не "отменить платеж" (между системами заказа (order) и платежа (payments)) а "я считаю, что состояние платежа 123 должно быть Cancelled". Обмен идет в рамках состояний (целевых в запросе на изменение, "актуальных" в ответах). Такой протокол неплохо ложится на современные реалии с потенциальными гонками между клиентами. "Операционная" семантика все равно предполагает какие-то исходные ("операция применима") и целевые ("результат применения") состояния. Так зачем описывать переход, если можно описать цель? И предоставить другой системе разбираться о том, как же достичь целевого состояния в рамках её модели.

Выше мы свели всю модель к задаче достижения консенсуса в распределенной среде. У каждого актора (системы) есть свое представление о том, какой должна быть "сущность". Акторы общаются друг с другом с целью получения консенсуса. Обычно для каждого типа сущностей имеется "главная" (authoritative), которая является абсолютной истиной и дргуие пытаются согласовать свое внутреннее состояние с тем, что может или не может быть в этом главном хранилище. Например, "главное" состояние платежей будет в payments, которая обеспечивает интеграцию с провайдерами. Да, я свел все к сложной задаче. И я считаю, что нет смысла решать простую задачу, если можно решать сложную. Даже если 90% случаев — простые, остаются 10% сложных случаев. В большой корпоративной среде 10% систем/коммуникаций явно будет больше 0. Т.е. сложную задачу придется решать. И будет лучше, если команды тренированы и уже знают, с какой стороны подходить к проблеме.

В рамках данной модели "payments" — главное состояние. Клиент "просит" получить целевое состояние. (Да, глагол можно выбрать и получше).

SD>Аналогично, следует ли разделять запрос и ответ семантически? Т.е. что должен делать paymentSvc, тоже писать "notify <???> of paymentReply"? Или как-то иначе, типа "reply paymentReply to <???>"

Да, стоит. Есть ассиметрия в модели (главное/авторитетное состояние и остальные). И есть практическое соображение об отсутствии циклических зависимостей между сервисами. Если orders знает о payments, то payments не должен ничего знать об orders. Поэтому и каналы "асимметричны". Запросы у нас получаются точка-точка (point-to-point). А вот ответы — это publish-subscribe. В этом случае все клиенты payments могут подписаться на изменения в состоянии платежа и соответствующим образом корректировать свои дальнейшие ожидания.

В нашем примере обмен будет следующим:
Синхронный ответ вообще говоря не обязателен. Т.е. payments может ответить "да, принято" и потом order будет ждать нового состояния и сравнивать с тем, что он хотел. Но с практической точки зрения удобно определять конфликты сразу в момент запроса. Особенно — при создании новых сущностей. Есть и сценарии, когда нужно ответить синхронно потому, что где-то далеко ждет браузер. Например, проведение платежа может потребовать авторизации на сайте платежной системы. В этом случае payments может ответить "да, принято, текущее состояние платежа ID=123 — требуется авторизация на <URL>". При этом в ответах payments все равно сообщает текущее состояние с некоторой дополнительной информацией. Синхронный обмен здесь скорее для удобства чем для функциональности.

Отсюда и наблюдаемая асимметрия в примерах. Notify — это запрос к авторитетной системе. Он может привести к (синхронному) конфликту. Или (в зависимости от API) к асинхронному выполнению (http 202 — accepted). А вот await — это предикативное ожидание какого-либо состояния в авторитетной системе. Т.е. прослушивание событий из темы (topic) и реакция на них.
Re: Никогда не недооценивайте силу по умолчанию
От: Pauel Беларусь http://blogs.rsdn.org/ikemefula
Дата: 31.10.22 08:30
Оценка:
Здравствуйте, Caracrist, Вы писали:

C>Из моего опыта по умолчанию должно быть примерно так:


C>

C>
Какой язык мне посоветуете?
C>

Для этого не нужен какой то особый язык, т.к. достаточно конвенции.
Re[10]: Никогда не недооценивайте силу по умолчанию
От: Sharov Россия  
Дата: 31.10.22 13:43
Оценка:
Здравствуйте, maxkar, Вы писали:

M>Отсюда и наблюдаемая асимметрия в примерах. Notify — это запрос к авторитетной системе. Он может привести к (синхронному) конфликту. Или (в зависимости от API) к асинхронному выполнению (http 202 — accepted). А вот await — это предикативное ожидание какого-либо состояния в авторитетной системе. Т.е. прослушивание событий из темы (topic) и реакция на них.


Вы предлагаете повесить на все это дело типизацию? Т.к. не ясно чем сущ. языки и библиотеки не подходят?
Если да, то почему с исп. соотв. языка будет проще, чем с соотв. языком+библиотекой?
Кодом людям нужно помогать!
Re[6]: Никогда не недооценивайте силу по умолчанию
От: _FRED_ Черногория
Дата: 31.10.22 16:34
Оценка:
Здравствуйте, netch80, Вы писали:

N>(Property стиля C# решают это же косвенно, но ломая ABI для тех модулей, что уже скомпилировались с прямым доступом. Или там это обошли?)


Что именно нужно было обходить? Добавить, например, set-тер к свойству можно, не требуя перекомпиляции использующего кода.

N>Кстати, я таки думаю, что самый правильный стиль из этих таки не в C++ private/protected/public, а в Java/C#, где умолчанием является package-internal.


В C# умолчанием является private для членов типа, а "package-internal" для типов уровня сборки.
Help will always be given at Hogwarts to those who ask for it.
Re[11]: Никогда не недооценивайте силу по умолчанию
От: maxkar  
Дата: 02.11.22 09:50
Оценка:
Здравствуйте, Sharov, Вы писали:

S>Вы предлагаете повесить на все это дело типизацию?

Со временем — и типизацию. С ней как раз проблем особых нет и в существующей инфраструктуре.

S>Т.к. не ясно чем сущ. языки и библиотеки не подходят?

Конкретно данный вызов Notify:

S>Если да, то почему с исп. соотв. языка будет проще, чем с соотв. языком+библиотекой?

Здесь ценность даже не столько в языке, сколько в рантайме. Фокусы, подобные выше, легко изначально закладывать в среду выполнения но очень сложно добавлять потом в виде "библиотек". Ну и прочие вещи вроде ожидания с предикатом (wait в примерах здесь
Автор: maxkar
Дата: 19.09.22
). Я не собираюсь держать в памяти одного процесса кучу ожиданий с предикатами — это ненадежно.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.