Ну и пусть. Если программист пишет новый класс extends Order, значит ему так надо для его задачи.
Если программист делает ошибки, значит ему так надо для его задачи.
У меня это задано с помощью функции isFinalizeAllowed, не вижу причин почему это надо делать именно разными типами.
Чтобы компилятор видел, опять же.
Описать 10 типов сложнее, чем 1.
Какие 10 типов у меня указано выше? У меня выше ровно два типа: тип, описывающий возможные состояния, и тип заказа. На сдачу ещё можно функцию, вычисляющую тип billing address'а по состоянию, посчитать, хз, но это тоже O(1) от количества состояний.
Отмена может быть возможна из 6 разных состояний, с типами придется их все указывать в аргументе и обновлять при добавлении нового разрешенного для отмены.
С чего бы?
CanCancel :: State → Bool
CanCancel Finalized = True
CanCancel Building = True
CanCancel _ = False
cancel :: CanCancel st ~ True ⇒ Order st → Order Cancelled
Добавляете просто ещё один кейс в CanCancel.
А для одного из них есть дополнительный критерий отмены, который надо проверять логикой, и часть требований будет в типах, а часть в алгоритме.
Нет, зачем?
То есть сложность не уменьшается, а переходит из одной формы в другую.
И это — непосредственная сложность задачи, потому что она непосредственно связана со спекой. Вы можете просто взять и показать все эти типы бизнес-аналитику, и с небольшой тренировкой на полчаса он сможет их прочитать (и только их) и перепроверить, что логика вроде как имеет смысл.
Это практически то, о чём пишут всякие дяди бобы, только сделанное по-нормальному и проверяемое компилятором.
Я все-таки имел в виду без этого. Вы не убираете необходимость лезть в детали, а вытаскиваете их наружу в виде типа, уменьшая уровень абстракции.
Абстракция — не самоцель, не все абстракции одинаково полезны. Я не хочу абстрагировать непосредственную бизнес-задачу, чтобы она была где-то внутри, а снаружи торчали абстрактные фабрики декораторов спринг-бинов.
Они подбираются под бизнес-требования, но выражают формат данных, которые лежат в переменной.
Формат данных и его семантику. Целое число. Положительное число. Делитель вон того числа.
А зачем нужно-то, чтобы проверял именно компилятор? Можно же использовать другой инструмент. Он не лучше или хуже, а просто другой.
И какие инструменты дадут мне по рукам за написание ерунды и помогут писать правильный код прямо во время написания кода, до того, как я не то что код в продакшен выкатил, а даже файл сохранил?
Ну так и с ФП можно написать только 1 тип, а не как у вас.
Ну да. И с ООП можно всё написать в одном классе, прямо в public static void Main.
Только в ООП тяжело требовать от компилятора проверок, а в типизированном ФП — легко.
Вот я и говорю, если мы пишем новый код, например добавляем метод shipOrder, то никакой существующий код нам не помешает случайно написать shipOrder :: Order Paid → Order Finalized.
Помешает, если алгебра работы с ордерами прописана в отдельном модуле (который менять не надо, и который не экспортирует кишки ордеров), а конкретные сценарии работы с ними вы описываете снаружи.
наследуем его, переопределяем, если надо для него enter() и exit() - то что будет обрабатываться при переходе в стейт и покидании его.
Подождите, я не понимаю. Как вы разделяете, что идёт в enter «следующего» состояния, а что — в exit «предыдущего»?
Вот у меня есть, опять же, пусть простой случай — заказ в состоянии Finalized. При переходе в состояние Paid надо проверить, что платёжные реквизиты норм, и выслать письмо покупателю. Что тут куда идёт?
дополняем fsm методом gotoSaving()
Кто такой fsm? Это какой-то класс-менеджер? Инстанс конкретной стейтмашины, построенной по данным бизнес-правилам?
реализуем логику перехода в новый стейт, там, где это надо по задаче
«А теперь рисуем сову»
Кто это вызывает? Это вызывается снаружи fsm? Какова его роль тогда? В чём разница между gotoSaving и Saving::enter?
Никак, так же как у вас. Сколько сделали типов, столько и будет. Зачем их ограничивать?
У меня количество состояний ограничено, новые добавить нельзя «снаружи», без модификации типа State. У вас — можно.
new с копированием полей. Потому так никто и не делает.
Тяжеловесный синтаксис мешает выражать мысли, не удивлён.
Так мы сами задаем осмысленные операции, а не компилятор.
Мы задаём (постулируем) атомарные операции. Осмысленность их композиции проверяет компилятор.
Да и даже осмысленность их задания компилятор проверить может — выявляемый косяк с размером фрейма композиции энкодеров я описывал рядом.
Обычно надо сделать новую функциональность, а не вызвать существующую, потому что существующая и так уже вызывается в существующем коде.
Новая функциональность может быть сделана в терминах «описать пайплайн для заказов со скидками и промо-акциями». И тут type-driven development вместе с type-driven-рефакторингом очень помогают.
В том и дело, что finalizeOrder совсем необязательно должна ставить какой-то статус. Что надо делать, описано в бизнес требованиях.
В рамах данной модельной задачи финализированный ордер — это отдельная сущность, потому что к нему применим строго определённый (и отличный от других состояний ордеров) набор операций.
Всё в типе не опишешь.
Опишешь :]
Но даже если бы это было не так,
Там много всего, и получения нужной информации все равно надо смотреть реализацию.
— стремление снизить необходимость лезть в детали реализации ИМХО осмысленно и вполне естественно.
Точно так же, как знание того, что функция принимает int, не отменяет необходимости понимать, что она потом с ним делает, но при этом всё равно оказывается очень полезным.
Типы переменных это техническая информация, а статусы заказа это бизнес-требования.
Типы переменных тоже выражают бизнес-требования. Телефон — скорее строка (а не число) потому, что «phone 0123» ≠ «phone 123». Количество компьютеров на складе — целое число потому, что у вас не может быть пол-компьютера. Деньги в трейдинговой системе выражаются как fixed point с N разрядами после точки потому, что таковы бизнес-требования.
Можно пытаться изложить бизнес-требования средствами для технической информации, но мы возвращаемся к вопросу "зачем?".
Для ответа придётся вернуться к тезису «чтобы компилятор проверял их выполнение.»
Так в императивном коде это тоже будет отдельная новая функция, сразу бросающаяся в глаза.
Не обязательно. Это вполне может быть скрыто где-то в деталях реализации в ООП-коде. ООП-код вида
Вы путаете конкретную задачу конкретного магазина и модельную задачу выражения стейтмашины. Вас просят решить задачу «Вася купил пять яблок за десять рублей, сколько стоит яблоко?», а вы начинаете рассказывать, что яблоки бывают разные, и вообще владелец магазина братан Васи и дал ему одно яблоко бесплатно.
Можно сделать классы OrderBuilding extends Order, OrderFinalized extends Order, но обычно так никто не делает.
Как тогда ограничить количество наследников-«состояний»?
И, кстати, из любопытства, как будет выглядеть создание OrderFinalized из OrderBuilding в вашем любимом языке? У них одинаковые поля, и в хаскеле я с RecordWildcards могу сделать finalize Order{..} = Order{..}, компилятор мне сам все поля перепакует (и при компиляции вообще превратит эту функцию в noop, потому что увидит, что не меняется ничего, кроме типов).
Да, значение поля в типах не проверяется. А надо? А зачем?
Чтобы компилятор проверил отсутствие заведомо бессмысленных операций или переходов.
И для того, чтобы у меня были явные подсказки, что я сейчас могу сделать дальше с заказом. В чуть более сложных стейтмашинах/бизнес-логике это помогает сильнее, чем может показаться на первый взгляд: часто вообще тупо из типов становится понятно, что делать, и подход «подумал над предметной областью ⇒ записал типы ⇒ выключил мозг и пишешь реализации» приводит к успеху.
В коде явно указано, что finalizeOrder поставит только статус Finalized и никакой другой.
Для того, чтобы сделать такой вывод из ООП-кода, мне надо читать весь код, включая детали реализации вроде «а какая тема письма, отправляемого пользователю, с уведомлением об отправке заказа». Чтобы сделать такой вывод в типизированном ФП, вы просто читаете типы функций, всё остальное проверяет компилятор.
Это примерно как если бы JS-разработчик из середины нулевых спросил «а зачем вам в функции объявлять, что она принимает только инт, и количество аргументов указывать? видно же из кода».
И с типами точно так же, берем и делаем метод setFinalized :: Order Shipped → Order Finalized
Это отдельная функция, сразу бросающаяся в глаза. Практика показывает, что так почему-то косячить сложнее, чем когда оно скрыто где-то в глубинах реализации.
У заказов есть состояния: составляется, закончил составляться (финализирован), оплачен, отправлен. Во всех состояниях у заказа есть адрес отправки и назначенные товары. В состояниях «оплачен» и «отправлен» у заказов ещё есть адрес выставления счёта, во всех прочих состояниях он просто бессмысленен.
Заказ создаётся в состоянии «составляется». В этом же состоянии (и только в нём) в него можно добавлять товары. Из этого же состояния его можно финализировать. После того, как заказ финализирован, его можно оплатить (и вы либо получите ошибку оплаты, либо заказ перейдёт в состояние «оплачен»). После того, как заказ оплачен, его можно отправить покупателю. Никаких других переходов в системе нет.
Но да, очевидно можно, если приведённый код делает что-либо осмысленное
Я написал только типы, потому что всё самое интересное в типах. Реализации функций тут очень скучные, вроде
В неискусственных задачах лучше выходит. Вот, например, кусок NFA-интерпретатора с иммутабельными векторами:
match :: StateId q => NFA q -> BS.ByteString -> MatchResult Int
match NFA{..} bs = go initState 0 mempty
where
go q i stack
| q == finState = SuccessAt i
| otherwise = case q `getTrans` transitions of
TEps q' -> go q' i stack
TBranch q1 q2 -> go q1 i (stack `V.snoc` (q2, i))
TCh ch q'
| bs `BS.indexMaybe` i == Just ch -> go q' (i + 1) stack
| Just (stack'', (q'', i'')) <- V.unsnoc stack -> go q'' i'' stack''
| otherwise -> Failure
Вот он же с мутабельными линейными:
match :: forall q. StateId q => NFA q -> BS.ByteString -> MatchResult Int
match NFA{..} bs = L.unur L.$ VL.empty L.$ go initState 0
where
go :: q -> Int -> VL.Vector (q, Int) %1-> L.Ur (MatchResult Int)
go q i stack
| q == finState = stack `L.lseq` L.Ur (SuccessAt i)
| otherwise = case q `getTrans` transitions of
TEps q' -> go q' i stack
TBranch q1 q2 -> go q1 i L.$ (q2, i) `VL.push` stack
TCh ch q'
| bs `BS.indexMaybe` i == Just ch -> go q' (i + 1) stack
| otherwise -> case VL.pop stack of
(L.Ur top, stack'')
| (Just (q'', i'')) <- top -> go q'' i'' stack''
| otherwise -> stack'' `L.lseq` L.Ur Failure
Проблема в том, что Страуструп не то что не знает, а просто не понимает проблему. А проблема не в том, насколько просто писать безбажный код (хинт: сложно, что бы там Страуструп ни говорил). Проблема в том, что писать бажный, падающий, дырявый код просто.
Страуструп — в первую очередь учитель, поэтому задачи и проблемы, которые он видит — это чтобы «ученикам» можно было легко и просто показывать примеры простого, безбажного кода (и поэтому его характерная реакция сводится к «правильно показывайте пишите, неправильно не показывайте пишите»). Добавили там какой-нибудь std::span — всё, его проблема решена, потому что в учебных примерах можно им пользоваться (а плохим не пользоваться). То, что в продакшен-коде есть легаси, которое этим не пользуется, есть люди с разным бекграундом, с разным состоянием выспанности, в конце концов — это всё неважно. Страуструп смотрит на наличие способов не выстрелить себе в ногу, а не на отсутствие способов выстрелить.
Все эти сильные стороны неважны и не исправляют ситуацию, покуда есть слабые.
поэтому важно улучшать инструменты и процессы разработки
Самый лучший вариант в моём опыте — улучшить инструмент разработки через смену ЯП.
data State
= Building
| Finalized
| Paid
| Shipped
BillingAddressType :: State → Type
BillingAddressType Paid = BillingAddress
BillingAddressType Shipped = BillingAddress
BillingAddress _ = ()
data Order state = Order
{ shippingAddress :: ShippingAddress
, goods :: [Item]
, billingAddress :: BillingAddressType state
}
createOrder :: ShippingAddress → Order Building
addItems :: [Item] → Order Building → Order Building
finalizeOrder :: Order Building → Order Finalized
payOrder :: PaymentInfo → Order Finalized → Either PaymentError (Order Paid)
shipOrder :: Order Paid → Order Shipped
Можно набросать соответствующую стейтмашину на чистом ООП?
(это было в ответ на
fsm и ООП никоим образом не противоречат друг другу, а наоборот, очень гармонично взаимно дополняют
а замечательный новый редактор комментариев не даёт добавить ньюлайны в начало комментария с кодовым листингом, по крайней мере, в FF — веб, который мы заслужили)
Людей, способных заниматься сексом, стоя в гамаке и на сноуборде тоже мало, и что за business value в этой наркомании с выкрутасами?
Это всё происходит во время компиляции, поэтому компилятор лучше оптимизирует то, что получается, поэтому перф выходит сильно лучше, чем если писать «просто». В некоторых задачах это ценят куда больше, чем поддерживаемость кода широким кругом лиц (тогда б изначально на плюсах не писали).
Можно просто сложный язык и/или сложную предметную область. Современный C++ — сложный. Людей, могущих в выкрутасы на темплейтах и оптимизированный код, мало. Разрабатывать свои языки — сложно. Людей, могущих сформулировать систему типов и доказать её хорошие свойства (или найти в ней проблемы), мало.
Во всех этих случаях не обязательно быть совсем уж ассенизатором-специалистом по легаси.
Рынок ит за границей очень сильно поменялся за 2 года.
Причём тут два года? Это с ближайшей осени, несколько месяцев назад.
Но реально эти деньги никто не поставит в офер)))
А если поставить больше смайликов, то это будет ещё истиннее и универсальнее.
Я же прямо написал, что эти цифры в офферах вполне дают, и это мой личный опыт. Более того, при некоторых условиях дают чуть выше вилки, чтобы скомпенсировать некоторые неожиданные налоговые вопросы.
Будут прогибать, и прогнут очень сильно....ибо рынок работодателя.
Опять же, смотря на какие позиции. В интересных мне позициях (HFT-клоунада по верхней грани описанных в моём изначальном комментарии зарплат на руки или разработка компиляторов, тайпчекеров, и так далее, по нижней) как-то и не прогибают, и рынок едва ли работодателя.
Как мне хороший знакомый после 6 месяцев поиска сказал - я с позиции тимлида гугла (откуда его уволили после 4 лет работы ибо все направление закрыли а общий стаж в ит 17 лет) ушел на позицию синьера разраба в теслу на деньги мидла.
Гугл, наверное, призван был произвести впечатление, но я нему отношусь резко негативно что по уровню тамошних спецов, что по некоторым другим причинам, поэтому, ну, ок, ушёл и ушёл. Бывает. Люди вон на интервью тоже всякое говорят.
И повторюсь, в нормальную контору вас никто не возмет без разрешения на работу, в стране оформления. Могут взять только через прокладку на краткосрочный контракт в стране где вы сидите, но денег будет принципиально меньше.
Не обязательно. Просто отправляете инвойс в фирму каждый месяц, и всё.
Учитывая разницу в налогах, денег будет даже больше (по крайней мере, это мой опыт работы не на W2, находясь изнутри США — там, во-первых, даже I-9 не спрашивают, а, во-вторых, эффективная налоговая ставка становится меньше 10%, и работодателю это выгодно, потому что он не должен заморачиваться со своей половиной FICA/SS/чётам).
Это не социализм, который в любом существующему на данный момент варианте отлично работает с монополиями (например в стиле «будете выпендриваться — посадим верхушку»).
Кто именно эти самые «мы», которые «посадим», в случае, когда соцгосударство и есть корпорация-монополист в худшем смысле этого слова?
import Data.Unrestricted.Linear qualified as L
import Data.Vector.Mutable.Linear qualified as VL
import Prelude.Linear qualified as L
fillVec :: Int → VL.Vector Int %1→ VL.Vector Int
fillVec n = go 0
where
go :: Int → VL.Vector Int %1→ VL.Vector Int
go v vec
| v == n = vec
| otherwise = go (v + 1) L.$ VL.push (v + 1) vec
swap :: Int → Int → VL.Vector a %1→ VL.Vector a
swap p1 p2 vec =
let %1 (L.Ur v1, vec¹) = VL.get p1 vec
%1 (L.Ur v2, vec²) = VL.get p2 vec¹
in VL.set p1 v2 L.$ VL.set p2 v1 vec²
main :: IO ()
main = do
let L.Ur vec = VL.empty (VL.freeze L.. swap 0 (n - 1) L.. fillVec n)
print vec
where n = 5
> :main
[5,2,3,4,1]
Или идрис:
import Data.Linear
import Data.Linear.Array
run : Int -> IO ()
run size = newArray size (\1 arr : _ => toIArray (swap 0 (size - 1) $ fill 0 arr) printArr)
where
fill : Int -> LinArray Int -@ LinArray Int
fill n arr = if n == size
then arr
else let _ # arr = write arr n (n + 1) in fill (n + 1) arr
swap : Int -> Int -> LinArray Int -@ LinArray Int
swap p1 p2 arr =
let mv1 # arr = mread arr p1
mv2 # arr = mread arr p2
in case (mv1, mv2) of
(Just v1, Just v2) => let _ # arr = write arr p1 v2
_ # arr = write arr p2 v1
in arr
_ => arr
printArr : IArray Int -> IO ()
printArr arr = for_ [0..size - 1] $ \i => case read arr i of
Just v => printLn v
Nothing => pure ()
т.е. всё-таки мы получаем ссылку на другую область памяти, а не изменяем данные in-place?
Нет, почему? Область памяти та же, просто имя другое.
Условно, в хаскеле с линейными типами операция записи в массив выглядит как
write :: Int → a → Array a %1→ Array a
где вот это вот %1→ означает, что переданным именем после вызова этой функции пользоваться нельзя, и надо пользоваться тем, что функция вернула (несмотря на то, что и старое, и новое имя ссылаются на один и тот же адрес).
По аналогичным причинам чтение выглядит как, несколько упрощая,
read :: Int → Array a %1→ (a, Array a)
и старым именем пользоваться нельзя, хотя чтение-то уж точно ничего не копирует.
Если программист делает ошибки, значит ему так надо для его задачи.
Чтобы компилятор видел, опять же.
Какие 10 типов у меня указано выше? У меня выше ровно два типа: тип, описывающий возможные состояния, и тип заказа. На сдачу ещё можно функцию, вычисляющую тип billing address'а по состоянию, посчитать, хз, но это тоже O(1) от количества состояний.
С чего бы?
Добавляете просто ещё один кейс в
CanCancel.Нет, зачем?
И это — непосредственная сложность задачи, потому что она непосредственно связана со спекой. Вы можете просто взять и показать все эти типы бизнес-аналитику, и с небольшой тренировкой на полчаса он сможет их прочитать (и только их) и перепроверить, что логика вроде как имеет смысл.
Это практически то, о чём пишут всякие дяди бобы, только сделанное по-нормальному и проверяемое компилятором.
Абстракция — не самоцель, не все абстракции одинаково полезны. Я не хочу абстрагировать непосредственную бизнес-задачу, чтобы она была где-то внутри, а снаружи торчали абстрактные фабрики декораторов спринг-бинов.
Формат данных и его семантику. Целое число. Положительное число. Делитель вон того числа.
И какие инструменты дадут мне по рукам за написание ерунды и помогут писать правильный код прямо во время написания кода, до того, как я не то что код в продакшен выкатил, а даже файл сохранил?
Ну да. И с ООП можно всё написать в одном классе, прямо в public static void Main.
Только в ООП тяжело требовать от компилятора проверок, а в типизированном ФП — легко.
Помешает, если алгебра работы с ордерами прописана в отдельном модуле (который менять не надо, и который не экспортирует кишки ордеров), а конкретные сценарии работы с ними вы описываете снаружи.
Подождите, я не понимаю. Как вы разделяете, что идёт в
enter«следующего» состояния, а что — вexit«предыдущего»?Вот у меня есть, опять же, пусть простой случай — заказ в состоянии
Finalized. При переходе в состояниеPaidнадо проверить, что платёжные реквизиты норм, и выслать письмо покупателю. Что тут куда идёт?Кто такой fsm? Это какой-то класс-менеджер? Инстанс конкретной стейтмашины, построенной по данным бизнес-правилам?
«А теперь рисуем сову»
Кто это вызывает? Это вызывается снаружи fsm? Какова его роль тогда? В чём разница между
gotoSavingиSaving::enter?Тяжело представить. Можете всё-таки показать пример? Я вот потрудился и набросал немного наиболее характерного кода.
У меня количество состояний ограничено, новые добавить нельзя «снаружи», без модификации типа State. У вас — можно.
Тяжеловесный синтаксис мешает выражать мысли, не удивлён.
Мы задаём (постулируем) атомарные операции. Осмысленность их композиции проверяет компилятор.
Да и даже осмысленность их задания компилятор проверить может — выявляемый косяк с размером фрейма композиции энкодеров я описывал рядом.
Новая функциональность может быть сделана в терминах «описать пайплайн для заказов со скидками и промо-акциями». И тут type-driven development вместе с type-driven-рефакторингом очень помогают.
В рамах данной модельной задачи финализированный ордер — это отдельная сущность, потому что к нему применим строго определённый (и отличный от других состояний ордеров) набор операций.
Опишешь :]
Но даже если бы это было не так,
— стремление снизить необходимость лезть в детали реализации ИМХО осмысленно и вполне естественно.
Точно так же, как знание того, что функция принимает
int, не отменяет необходимости понимать, что она потом с ним делает, но при этом всё равно оказывается очень полезным.Типы переменных тоже выражают бизнес-требования. Телефон — скорее строка (а не число) потому, что «phone 0123» ≠ «phone 123». Количество компьютеров на складе — целое число потому, что у вас не может быть пол-компьютера. Деньги в трейдинговой системе выражаются как fixed point с N разрядами после точки потому, что таковы бизнес-требования.
Для ответа придётся вернуться к тезису «чтобы компилятор проверял их выполнение.»
Не обязательно. Это вполне может быть скрыто где-то в деталях реализации в ООП-коде. ООП-код вида
просто потому, что так проще, я видел.
На хаскеле можно (и очень приятно) делать DSL'и в том числе для глубокого эмбеддеда: например, ivory.
Вы путаете конкретную задачу конкретного магазина и модельную задачу выражения стейтмашины. Вас просят решить задачу «Вася купил пять яблок за десять рублей, сколько стоит яблоко?», а вы начинаете рассказывать, что яблоки бывают разные, и вообще владелец магазина братан Васи и дал ему одно яблоко бесплатно.
Рантайм-проверка же. Это совсем не то же самое.
Как тогда ограничить количество наследников-«состояний»?
И, кстати, из любопытства, как будет выглядеть создание
OrderFinalizedизOrderBuildingв вашем любимом языке? У них одинаковые поля, и в хаскеле я сRecordWildcardsмогу сделатьfinalize Order{..} = Order{..}, компилятор мне сам все поля перепакует (и при компиляции вообще превратит эту функцию в noop, потому что увидит, что не меняется ничего, кроме типов).Чтобы компилятор проверил отсутствие заведомо бессмысленных операций или переходов.
И для того, чтобы у меня были явные подсказки, что я сейчас могу сделать дальше с заказом. В чуть более сложных стейтмашинах/бизнес-логике это помогает сильнее, чем может показаться на первый взгляд: часто вообще тупо из типов становится понятно, что делать, и подход «подумал над предметной областью ⇒ записал типы ⇒ выключил мозг и пишешь реализации» приводит к успеху.
Для того, чтобы сделать такой вывод из ООП-кода, мне надо читать весь код, включая детали реализации вроде «а какая тема письма, отправляемого пользователю, с уведомлением об отправке заказа». Чтобы сделать такой вывод в типизированном ФП, вы просто читаете типы функций, всё остальное проверяет компилятор.
Это примерно как если бы JS-разработчик из середины нулевых спросил «а зачем вам в функции объявлять, что она принимает только инт, и количество аргументов указывать? видно же из кода».
Это отдельная функция, сразу бросающаяся в глаза. Практика показывает, что так почему-то косячить сложнее, чем когда оно скрыто где-то в глубинах реализации.
Вот я бы хотел, чтобы человек показал этот вариант хотя бы самому себе.
Пишем что-то про заказы.
У заказов есть состояния: составляется, закончил составляться (финализирован), оплачен, отправлен. Во всех состояниях у заказа есть адрес отправки и назначенные товары. В состояниях «оплачен» и «отправлен» у заказов ещё есть адрес выставления счёта, во всех прочих состояниях он просто бессмысленен.
Заказ создаётся в состоянии «составляется». В этом же состоянии (и только в нём) в него можно добавлять товары. Из этого же состояния его можно финализировать. После того, как заказ финализирован, его можно оплатить (и вы либо получите ошибку оплаты, либо заказ перейдёт в состояние «оплачен»). После того, как заказ оплачен, его можно отправить покупателю. Никаких других переходов в системе нет.
Я написал только типы, потому что всё самое интересное в типах. Реализации функций тут очень скучные, вроде
Так что, можно теперь ООП-вариант?
В неискусственных задачах лучше выходит. Вот, например, кусок NFA-интерпретатора с иммутабельными векторами:
Вот он же с мутабельными линейными:
Проблема в том, что Страуструп не то что не знает, а просто не понимает проблему. А проблема не в том, насколько просто писать безбажный код (хинт: сложно, что бы там Страуструп ни говорил). Проблема в том, что писать бажный, падающий, дырявый код просто.
Страуструп — в первую очередь учитель, поэтому задачи и проблемы, которые он видит — это чтобы «ученикам» можно было легко и просто показывать примеры простого, безбажного кода (и поэтому его характерная реакция сводится к «правильно
показывайтепишите, неправильно непоказывайтепишите»). Добавили там какой-нибудьstd::span— всё, его проблема решена, потому что в учебных примерах можно им пользоваться (а плохим не пользоваться). То, что в продакшен-коде есть легаси, которое этим не пользуется, есть люди с разным бекграундом, с разным состоянием выспанности, в конце концов — это всё неважно. Страуструп смотрит на наличие способов не выстрелить себе в ногу, а не на отсутствие способов выстрелить.Все эти сильные стороны неважны и не исправляют ситуацию, покуда есть слабые.
Самый лучший вариант в моём опыте — улучшить инструмент разработки через смену ЯП.
Можно набросать соответствующую стейтмашину на чистом ООП?
(это было в ответ на
а замечательный новый редактор комментариев не даёт добавить ньюлайны в начало комментария с кодовым листингом, по крайней мере, в FF — веб, который мы заслужили)
Это всё происходит во время компиляции, поэтому компилятор лучше оптимизирует то, что получается, поэтому перф выходит сильно лучше, чем если писать «просто». В некоторых задачах это ценят куда больше, чем поддерживаемость кода широким кругом лиц (тогда б изначально на плюсах не писали).
Можно просто сложный язык и/или сложную предметную область.
Современный C++ — сложный. Людей, могущих в выкрутасы на темплейтах и оптимизированный код, мало.
Разрабатывать свои языки — сложно. Людей, могущих сформулировать систему типов и доказать её хорошие свойства (или найти в ней проблемы), мало.
Во всех этих случаях не обязательно быть совсем уж ассенизатором-специалистом по легаси.
А кто посадит Иосифа Виссарионовича, когда тот начнёт зарываться?
Кто проверит, что его посадки действительно делают лучше, а не хуже? А то там, знаешь, потом было всякое, пересмотры, реабилитации…
Ты в этом проблему видишь, я — нет. Что с этим делать будем? Сажать, если проигнорируют намёки, или нет?
Причём тут два года? Это с ближайшей осени, несколько месяцев назад.
А если поставить больше смайликов, то это будет ещё истиннее и универсальнее.
Я же прямо написал, что эти цифры в офферах вполне дают, и это мой личный опыт. Более того, при некоторых условиях дают чуть выше вилки, чтобы скомпенсировать некоторые неожиданные налоговые вопросы.
Опять же, смотря на какие позиции. В интересных мне позициях (HFT-клоунада по верхней грани описанных в моём изначальном комментарии зарплат на руки или разработка компиляторов, тайпчекеров, и так далее, по нижней) как-то и не прогибают, и рынок едва ли работодателя.
Гугл, наверное, призван был произвести впечатление, но я нему отношусь резко негативно что по уровню тамошних спецов, что по некоторым другим причинам, поэтому, ну, ок, ушёл и ушёл. Бывает. Люди вон на интервью тоже всякое говорят.
Не обязательно. Просто отправляете инвойс в фирму каждый месяц, и всё.
Учитывая разницу в налогах, денег будет даже больше (по крайней мере, это мой опыт работы не на W2, находясь изнутри США — там, во-первых, даже I-9 не спрашивают, а, во-вторых, эффективная налоговая ставка становится меньше 10%, и работодателю это выгодно, потому что он не должен заморачиваться со своей половиной FICA/SS/чётам).
Кто именно эти самые «мы», которые «посадим», в случае, когда соцгосударство и есть корпорация-монополист в худшем смысле этого слова?
Да. конечно. Вот хаскель:
Или идрис:
Нет, почему? Область памяти та же, просто имя другое.
Условно, в хаскеле с линейными типами операция записи в массив выглядит как
где вот это вот
%1→означает, что переданным именем после вызова этой функции пользоваться нельзя, и надо пользоваться тем, что функция вернула (несмотря на то, что и старое, и новое имя ссылаются на один и тот же адрес).По аналогичным причинам чтение выглядит как, несколько упрощая,
и старым именем пользоваться нельзя, хотя чтение-то уж точно ничего не копирует.