Pull to refresh
2
1.2

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

Send message

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

Страуструп — в первую очередь учитель, поэтому задачи и проблемы, которые он видит — это чтобы «ученикам» можно было легко и просто показывать примеры простого, безбажного кода (и поэтому его характерная реакция сводится к «правильно показывайте пишите, неправильно не показывайте пишите»). Добавили там какой-нибудь 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, и там всё вполне строго (очень хороший папир).

Ну автор же просил реальный пример где ООП нужно, я его привел.

Так ведь не нужно. Я вон без всяких ООП написал, и даже без смущающего слова class. Просто функции, функции, композиции функций. Классика ФП.

Но если вы пишите не число дробилку - это не так уж и часто.

Компиляторы — числодробилки? Их парсерная часть? Или те же перекладывалки жсонов уровня «запуститься по крону, взять данные из трёх БД, заджойнить по хитрой бизнес-логике и сложить в четвёртую»?

А как вы тут запишите, например

Начну с конца, с самого скучного.

И что у вас в системе не более 3 HW encoder создается.

Просто возвращая Maybe Encoder (или Either EncoderCreationError Encoder), а не Encoder. Это существенно нелокальное свойство, и создание энкодера может навернуться по куче других причин (включая софтварные): три HW encoder'а, или видяха из слота вытащилась (PCIe же поддерживает hotplug?), или для софтваре энкодера dlopen для x264 чё-т не работает. Я не вижу смысла выражать конкретно тройку конкретно в типах (но если вам очень хочется, то могу показать, как это делать).

А у второго HW енкодера надо обязательно хотя бы раз 100 Encode вызывать RequestKeyFrame.

С зависимыми типами можно выразить что угодно.

data State
  = ...
  | CanFeedNonKeyFrameCounted Nat -- параметр — оставшееся число не-кейфреймов
  
data EncoderMetadata = EncoderMetadata
  { maxNonKeyframes :: Maybe Nat
  }

-- есть более изящные способы, чем таскать `md` в типе,
-- но для маленького примера они не нужны
data Encoder md st = ...

loadVP9hwEncoder : IO (Encoder (EncoderMetadata (Just 100)) Initialized)
loadVP8hwEncoder : IO (Encoder (EncoderMetadata Nothing) Initialized)

feedNonKeyframe : Frame
                → Encoder md (CanFeedNonKeyframe (suc n))
                → Encoder md (CanFeedNonKeyframe n)
                
feedKeyframe : CanFeedKeyframeAt st
             ⇒ Frame
             → (e : Encoder md st)
             → Encoder md (case maxNonKeyframes md of Just n ⇒ CanFeedNonKeyframeCounted n
                                                      Nothing ⇒ CanFeedNonKeyframe)
-- или, если это решается динамически, тогда `Maybe` у maxNonKeyframes не нужен
-- и возвращаем тип-сумму из двух вариантов, по которым надо будет сделать рантайм-матч:
             → Encoder md (CanFeedNonKeyframeCounted (maxNonKeyframes md)) ∨ Encoder md CanFeedNonKeyframe

И что в оба HW encoder нельзя передавать видео фреймы размера меньше 240x180?

Совершенно аналогично:

data EncoderMetadata = EncoderMetadata
  { maxNonKeyframes :: Maybe Nat
  , minFrameSize :: Size
  }

feedNonKeyframe : (f : Frame)
                → Encoder md (CanFeedNonKeyframe (suc n))
                → {auto 0 _ : minFrameSize md ≤ frameSize f}
                → Encoder md (CanFeedNonKeyframe n)

...

у H264HWEncoder (и только его из всех 4) нельзя 2 раза подряд кодировать key-frame (нельзя иметь последовательность request kf, encode, request kf, encode, и нельзя иметь request kf перед вторым вызовом encode)

Совершенно аналогично (только в моём словаре нет encode, поэтому в виде кода я выражать не буду, но смысл понятен, надеюсь).

А если все енкодеры во время работы что-то пишут в общий логгер?

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

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

Запустил gcc под профайлером памяти на TUшке, которая компилируется пару минут и жрёт при этом гигов восемь памяти в пике — чуть больше терабайта аллокаций.

Не, память освобождать надо.

Я никогда не говорил, что математика не нужна для постижения X или Y. Я говорил (и говорю, и буду говорить), что математическое образование вне чуть-более-чем-школьного не нужно для ≈99% программистских задач и вакансий.

А в данном конкретном случае — ну не понял чего-то человек, ну и что? Разве он будет от этого хуже жить? Меньше есть? Чаще просыпаться по ночам? Станет ли он менее счастливым?

Нет.

Information

Rating
1,748-th
Registered
Activity