Pull to refresh
2
0.9

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

Send message

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

Посмотрел бы на этот алготрейдинг, если в моём алготрейдинге на каждый бранч смотрят как на источник вариабельной (и плохой) латентности, а там целый GC/JIT/непонятночто.

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

И по похожим причинам мой алготрейдинг по факту однопоточный.

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

Уже писал в другом треде, но повторюсь: я тоже асоциальный хиккан с нулём общения вне работы, но требовать от окружающих, чтобы они решали мои проблемы, мне в голову не приходит.

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

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

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

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

Говорить, что без людей невозможно валидировать рост скиллов в программировании (или матане) — это как говорить, что без людей невозможно валидировать рост силы, и что вы теперь жмёте не 10 килограмм, а 20.

И если это и путь в психушку, то только потому, что вы отрываетесь от социально общепринятых паттернов, а не потому, что посмотреть и оценить некому.

Линейные типы завезли уже очень давно.

Надо снова посмотреть, значит, спасибо за напоминание!

Авторы движков за ООП в движке вас сожгут на костре из P4 Prescott. Там топят за data-oriented design (который ООП не то что ортгонален, а даже слегка противоречит), и прочие подобные вещи, которые как раз на ФП ложатся отлично.

Вот характерный доклад.

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

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

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

Тут есть два аспекта.

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

Во-вторых, есть много других способов выражать предметные области. Просто показывать их в таких мелких примерах — плохая идея, потому что читатель увидит «наворотили какой-то хрени, академики, блин» и сделает не совсем верные выводы. В этом проблема с такими мелкими примерами, на самом деле.

Проблема ООП не в том, что вам не видно, есть там сайд-эффекты или нет. Если не использовать антипаттерн "миллионы глобальных флагов", то любой не-const метод меняет состояние класса.

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

Никак нельзя описать ограничения вроде "после вызова MethodA с такими-то параметрами, перед вызовом MethodC с такими-то параметрами, надо обязательно вызвать MethodB с такими-то параметрами". Так и в ФП, вы явно видите, что вот эта функция возвращает новый стейт, она его меняет, но как именно - не видно. В ФП, теоретически, вы могли бы описать это через возвращаемые типы, но на практике это невозможно, ибо комбинаторный взрыв и расписывать все внутренние состояния одного только энкодера вы задолбаетесь.

Почему?

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

data EncoderState
  = Initialized
  | Ready
  | KeyFrameExpected
  | AnyFrameExpected
  | LostStream

data Encoder s = Encoder { encLibHandle :: Handle }

load :: String → Either LoadError (Encoder Initialized)

configure :: Config → Encoder Initialized → Either ConfigError (Encoder Ready)

class CanFeedKeyFrameAt st
instance CanFeedKeyFrameAt Ready
instance CanFeedKeyFrameAt KeyFrameExpected
instance CanFeedKeyFrameAt AnyFrameExpected

feedKeyframe :: CanFeedKeyFrameAt st => Keyframe → Encoder st → Encoder LostStream ∨ Encoder AnyFrameExpected

feedNonKeyframe :: Frame → Encoder AnyFrameExpected → Encoder LostStream ∨ Encoder KeyFrameRequred ∨ Encoder AnyFrameExpected
-- или, если обмазываться завтипами
feedNonKeyframe :: Frame → Encoder AnyFrameExpected → Encoder LostStream ∨ Σ st. (Encoder st ∧ CanFeedKeyFrameAt st)

recover :: Encoder LostStream → Maybe (Encoder KeyFrameExpected)

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

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

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

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

Более того, есть либы вроде https://hackage.haskell.org/package/lvish , которые дают мутабельность, параллельность и детерминизм.

Да (правда, относительно давно). Мне не понравилось что-то в его теории типов — если я правильно помню, там есть Prop, для которого работает uniqueness of identity proofs (что ломает совместимость с HoTT — один минус), но при этом он не импредикативный (для несовместимой с HoTT теории это минус).

Это не те же классы и не те же инстансы. Подробнее я написал ниже здесь и в начале комментария здесь.

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

Кто запретит? Компилятор?

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

Какое ещё подключение к базе данных в классе (или функции, неважно), отвечающем за форматирование имени данного ему юзера? Это чтобы макаронный код потом веселее разгребать было?

Самое главное, как вы будете определять, какую функцию передавать в другую функцию в случае, если например, для одних настроек приложения надо использовать DisplayFormatter, а для других - RTFFormatter?

Так и буду.

data Options = Options
  { useRtf :: Bool
  } deriving (Eq, Show, Generic, ParseCliOptoins)

main :: IO ()
main = do
  Options{..} ← getCliOptions
  doThings (if useRtf then rtfFormatter else displayFormatter)

Есть такой же полиморфизм, что-то типа наследования (классы и инстансы)

На всякий случай, ещё раз подчеркну, что классы — это объявления контрактов/концептов, а инстансы — сообщение компилятору, что данный тип (или множество типов) удовлетворяет контракту, и описание, как именно они это делают. С ООП тут есть некоторое пересечение, но очень отдалённое.

Например, может быть класс и инстансы

class Mult left right result | left, right → result where
  multiply :: left → right → result

instance Mult (Matrix m n) (Matrix n k) (Matrix m k) where
  multiply = ...

instance Mult (Vect n) (Vect n) (Vect n) where
  multiply = ...

где в первой строке Mult left right result означает, что «контракт» описывает отношения трёх типов, а | left, right → result означает, что первые два типа автоматически определяют третий единственным образом (и компилятор это проверяет для инстансов и пользуется при тайпчекинге/выводе типов).

Я затрудняюсь выразить ровно это на ООП с такой же степенью симметрии.

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

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

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

Минусы, я думаю, тоже есть.

Зависит от задач, конечно, но я ни разу не ловил себя на желании «эх вот бы щас тут ООП-классик бы навернуть».

Надо будет функции передавать параметр конкретной операции? Или есть более простой механизм?

Зависит от более общего контекста. Можно так, да:

data Input
  = EncodeFrame VideoFrame
  | NotifyMissingInput
  | ReconfigureBitrate Bitrate

type Encoder = Input → IO (Either Error BitStream)

Можно

data Encoder m = Encoder
  { encodeFrame :: VideoFrame → m (Either Error BitStream)
  , notifyMissing :: m ()
  , reconfigureBitrate :: Bitrate → m ()
  }

— то есть, тупо рекорд (структура) с разными функциями, которая может создаваться через

vp8encoder :: Params → IO (Encoder IO)
vp8encoder params = do
  commonRef ← newIORef ...
  let encodeFrame = ...
      notifyMissing = ...
      reconfigureBitrate = ...
  pure $ Encoder {..}

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

Это выглядит совсем как ООП, только, опять же, так как это полноценные first-class-citizen-данные, можно легко их собирать из отдельных кусочков и легко модифицировать. Паттерны и языковые фичи вроде миксинов просто становятся не нужны.

криптография 2004-2006 года - это старый и сложный код?

Старый, но несложный.

все прекрасно собралось

Добавьте оптимизации:

% g++ -v |& grep "gcc version"
gcc version 14.2.1 20241221 (Gentoo 14.2.1_p20241221 p7)
% g++ -Wall -Wextra -O3 -DDERS_CPU=cpu -c key.cpp
[...]
In file included from uint.hpp:22,
                 from key.hpp:19,
                 from key.cpp:11:
In destructor 'sh_ptr<T>::~sh_ptr() [with T = UInt]',
    inlined from 'void Key::computeRemainders(ShUInt, int, std::vector<sh_ptr<UInt> >&)' at key.cpp:57:1:
sh_ptr.hpp:93:30: warning: '*(sh_ptr<UInt>::Rep*)<unknown>.sh_ptr<UInt>::Rep::refs' may be used uninitialized [-Wmaybe-uninitialized]
   93 |       ~sh_ptr() { if (--rep->refs==0) delete rep; }
      |                         ~~~~~^~~~

и отлично работает!

Не могу проверить, потому что хз с чем это запускать вообще.

Но зато ошибки видны просто невооружённым глазом. Например,

else return ShUInt();  // NULL

вызывает сначала конструктор sh_ptr<UInt> с нулевым указателем ptr, а потом его удаляет через в итоге fixed_alloc::free(ptr), который…

      static void free(void* ptr)
      {
       fixed_alloc_private::void_alloc<SIZE>::free(ptr);
      }

, который…

       static void free(void* ptr)
       {
        *(void**)ptr=head;
        head=ptr;
       }

Ой-вей, поздравляю с записью по нулевому указателю.

ЗЫ

ЗЫ — чувак написал 1400 строк кода, из которых треть — переизобретение своего наколенного boost::shared_ptr и прочего подобного, в которых сходу находятся ошибки, и пытается этим опровергнуть наличие проблем с миграцией на новые компиляторы у тех, кого строк кода не 1400, а 140000000.

Это, конечно, смешно.

может дело немножно в руках?

Это про любую технологию сказать можно. Дело всегда в руках, технологии всегда шикарные. Не можешь писать производительный код на JS? Да у тебя просто руки кривые.

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

За счёт того, что в агде паттерн-матчинг сделан ну вот прям в определении функций, там это всё куда лучше выглядит.

Что, конечно, не отменяет куда более лучшей автоматизации в коке, поэтому имеем то, что имеем.

Спасибо, но я все еще не понимаю, а где хранится FallbackState для каждого энкодера в системе?

Он хранится для каждого созданного «экземпляра» fallback-энкодера в замыкании соответствующей функции. Каждый раз, когда я пишу

encoder ← swFallbackEncoder vp9hw vp9sw

то у меня «запускается» тело swFallbackEncoder, которое создаёт (newIORef) мутабельную переменную (isFallback), где лежит FallbackState (CanUseHW по умолчанию), и возвращает лямбду, которая на каждый фрейм проверяет эту захваченную переменную.

На плюсах это выглядело бы примерно как

auto swFallbackEncoder(auto hw, auto sw)
{
  auto shouldFallback = std::make_shared<bool>(false);
  return [=](VideoFrame frame)
  {
    if (*shouldFallback)
      return sw(frame);
    auto res = hw(frame);
    if (!res)
    {
      res = sw(frame)
      *shouldFallback = true;
    }
    return res;
  };
}

Вот эта mkEncoder она могла вернуть fallback врапер, а могла просто энкодер. Как вы потом ее результат вызваете?

Как функцию :]

runWithConfig config = do
  maybeEnc ← mkEncoder (encoderName config)
  case maybeEnc of
    Nothing → putStrLn "uh oh"
    Just enc → enc frame -- вызываю!

Можно написать тестовый код вроде

vp9swEncoder :: IO (Maybe Encoder)
vp9swEncoder = pure $ pure $ \frame → putStrLn "doing sw..." >> pure (Right $ show frame)

vp9hwEncoder :: IO (Maybe Encoder)
vp9hwEncoder = pure $ pure $ \frame → putStrLn "trying hw…" >> pure (Left "unable to encode")

и потом

main :: IO ()
main = do
  -- лень проверять just/nothing, я знаю, что там just
  Just enc1 ← mkEncoder "vp9"
  Just enc2 ← mkEncoder "vp9"
  enc1 10 >>= print
  enc1 11 >>= print
  enc2 20 >>= print
  enc2 21 >>= print

выведет

trying hw…
doing sw...
Right "10"
doing sw...
Right "11"
trying hw…
doing sw...
Right "20"
doing sw...
Right "21"

обратите внимание — trying hw для каждого отдельного энкодера печатается только раз.

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

Зачем для этого класс? Какой у него стейт? Могу ли я один инстанс класса DisplayFormatter дёргать из разных тредов? Могу ли я разные инстансы дёргать из разных тредов? Эквивалентен ли DisplayFormatter, отформатировавший сто юзеров, свежесозданному?

А можно просто сделать чистую функцию formatUser : User → String, и там этих вопросов нет. И передавать эти функции как аргументы другим функциям (если очень хочется звучать умно, можно это тоже называть DI).

Ну давайте упрастим и возьмем ваш тривиальный пример.

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

Блин, извините, я идиот, зачем-то флаги держу, когда можно без флагов, и просто в стейте хранить саму функцию, которую надо вызывать. Так гораздо чище, и в кои-то веки пригодился Control.Monad.mfix:

swFallbackEncoder :: Encoder → Encoder → IO Encoder
swFallbackEncoder hwEncoder swEncoder = do
  runner ← mfix $ \runner → newIORef $ \frame → hwEncoder frame >>= \case
    Right bs → pure $ Right bs
    Left err → writeIORef runner swEncoder >> swEncoder frame
  pure $ \frame → readIORef runner >>= ($ frame)

Information

Rating
1,993-rd
Registered
Activity