Обновить
1

Специалист по теории типов USB-кабелей

1,4
Рейтинг
30
Подписчики
Отправить сообщение

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

Пишем что-то про заказы.

У заказов есть состояния: составляется, закончил составляться (финализирован), оплачен, отправлен. Во всех состояниях у заказа есть адрес отправки и назначенные товары. В состояниях «оплачен» и «отправлен» у заказов ещё есть адрес выставления счёта, во всех прочих состояниях он просто бессмысленен.

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

Но да, очевидно можно, если приведённый код делает что-либо осмысленное

Я написал только типы, потому что всё самое интересное в типах. Реализации функций тут очень скучные, вроде

createOrder addr = Order addr [] ()

addItems items Order{..} = Order{.., goods = goods <> items }

finalizeOrder Order{..} = Order{..}

Так что, можно теперь ООП-вариант?

В неискусственных задачах лучше выходит. Вот, например, кусок 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++ — сложный. Людей, могущих в выкрутасы на темплейтах и оптимизированный код, мало.
Разрабатывать свои языки — сложно. Людей, могущих сформулировать систему типов и доказать её хорошие свойства (или найти в ней проблемы), мало.

Во всех этих случаях не обязательно быть совсем уж ассенизатором-специалистом по легаси.

Иосиф Виссарионович легко посадит авиаконструктора, даже если в стране всего лишь 3 авиа КБ.

А кто посадит Иосифа Виссарионовича, когда тот начнёт зарываться?

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

И Зубину с Гамари легко намекнёт, что не надо ломать bootstrapp промышленного компилятора.

Ты в этом проблему видишь, я — нет. Что с этим делать будем? Сажать, если проигнорируют намёки, или нет?

Рынок ит за границей очень сильно поменялся за 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 ()
Main> :exec (run 5)
5
2
3
4
1

т.е. всё-таки мы получаем ссылку на другую область памяти, а не изменяем данные in-place?

Нет, почему? Область памяти та же, просто имя другое.

Условно, в хаскеле с линейными типами операция записи в массив выглядит как

write :: Int → a → Array a %1→ Array a

где вот это вот %1→ означает, что переданным именем после вызова этой функции пользоваться нельзя, и надо пользоваться тем, что функция вернула (несмотря на то, что и старое, и новое имя ссылаются на один и тот же адрес).

По аналогичным причинам чтение выглядит как, несколько упрощая,

read :: Int → Array a %1→ (a, Array a)

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

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

Да. Только это означает, что я пользовался инкапсуляцией. Говорить, что у ООП монополия на понятие инкапсуляции — это перебор (а мы до этого дойдём через несколько итераций).

у вас там тоже есть штука "Encoder" которая описывает набор функций, в точности как интерфейс в C++

Только Encoder — это просто алиас для типа. Мне не нужно особое понятие «интерфейс», оно становится частным случаем «любое значение, удовлетворяющее типу». Как обычно.

но в виде общего Encoder в точности как фабричный метод.

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

В точности как полиморфизм в ООП.

Нет, потому что в ООП навешаны какие-то принципы подстановки лисков (которые не работают), виртуальные функции, рантайм-диспатчинг, и так далее, а в ФП я просто передаю функции в функции, и полиморфизма здесь не больше, чем полиморфизма в функции int foo(int n) { return n + 2; }. которая игнорирует конкретное значение n. Как обычно.

Вы точно также добавили бы DumpingEncoder для сливания данных в файл, если он нужен.

Только это была бы просто функция, возвращающая функцию. Никакого наследования, никаких дум «а мне тут наследоваться или агрегировать? или, может, CRTP?» Просто работа как с обычными значениями. Снова и как обычно.

Вы отказались от class/instance потеряв какую-то часть проверок, ведь теперь вы можете в коде в качестве Encoder использовать и условное замыкание для шифрования пикселей, у которого по случайности и недосмотру главный метод также обозвали Encode().

Нет, не могу. Имена и их совпадения важны только для class/instance. Если я от них отказался, то важны только типы.

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

Только смысл в том, что ФП позволяет выражать сильно больше и проще, чем это позволяет ООП. Там, где в ООП нужны какие-то особые отдельные паттерны, про которые пишут книжки и спрашиввают на интервью, в ФП вы просто используете функции, как ни в чём не бывало. Ну, прямо как в том меме,

И говорить, что «дизайн системы в ФП отражает концепции из ООП» — ну да, а чего вы хотели, если там есть инъекция? Это как говорить, что «чего вы говорите, что вещественные числа полнее целых, если при решении 2x = 4 вы не пользовались вещественными числами?»

Но как это работает, когда у вас есть "интерфейс" Encoder? У вас же mkEncoder имеет конкретный тип - он возвращает Encoder.

Тогда mkEncoder возвращает Encoder и его «метаданные» через Σ-тип (то есть, пару, где тип второго компонента зависит от значения первого), например:

mkEncoder : String
          → IO (Σ md. Encoder md Initialized)
mkEncoder "vp9" = do
  hw ← loadVP9hwEncoder
  sw ← loadVP9swEncoder
  pure $ (_ , mkFallbackEncoder hw sw)

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

_⊓_ : EncoderMetadata
    → EncoderMetadata
    → EncoderMetadata
md1 ⊓ md2 = EncoderMetadata
              (maxNonKeyframes md1 ⊓ maxNonKeyframes md2)
              (minFrameSize md1 ⊔ minFrameSize md2)

mkFallbackEncoder : Encoder md1 Initialized
                  → Encoder md2 Initialized
                  → Encoder (md1 ⊓ md2) Initialized
mkFallbackEncoder = ...

где эти смешные значки ⊓ и ⊔ — это решёточные greatest lower bound и least upper bound, которые для количества кейфреймов сводятся к минимуму двух значений, а для минимального размера фрейма — к максимуму из двух.

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

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

Нет, проблема объективно существует. В тех же gcc и clang те же ADT переизобретены явно, насколько это можно сделать в C++.

В штатах сейчас в очередь в офис стоят за 10к. Просто очень в теме со стороны работодателя)

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

Тем временем мне достаточно регулярно приходят подобные вакансии:

(нижние две — это база, там такой же бонус предлагают). Подсчитать долю Remote вы можете сами, думаю. И это реальные офферы, которые можно получить.

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

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

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

Даже 450 в год даст 360 после налогов, если их оптимизировать по полной. Если не оптимизировать — получите столько на руки с примерно 530 (или с 480, если у вас неработающая жена).

Это удалёнка в Штатах на штатовские же компании.

А где вы видите требования.

В ваших комментариях.

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

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

Рабочую в каком смысле и по каким критериям?
Найти друзей? Совершенно очевидно, что ваш метод (офисная работа) тоже не работает, потому что друзья после выхода на удалёнку, судя по вашим словам, у вас отвалились.
Чтобы зеркальные нейроны приятно теребились? Общайтесь с людьми в интернете или, не знаю, вебкамщицы там к вашим услугам.

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

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

Не стоит вскрывать эту тему.

Вы бы хоть уточняли, куда завезли

Математическая теория — лет 40+. Clean — изначально там были, Idris — с ЕМНИП 0.9.15 (лет 8 уже), GHC — 9.0.1 (года четыре как).

и как там у них с мутабельностью?

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

Но статья утверждает, что строгие типы полиморфизма не нужны, типа есть поле - делай.

«Есть поле — делай» — это же row polymorphism, и там всё вполне строго (очень хороший папир).

Информация

В рейтинге
1 883-й
Зарегистрирован
Активность