Это означает, что код на Яве писали люди с недостаточной квалификацией, которые не знали характерные идиомы. Ява используется аж в алготрейдинге.
Посмотрел бы на этот алготрейдинг, если в моём алготрейдинге на каждый бранч смотрят как на источник вариабельной (и плохой) латентности, а там целый 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, и, упрощая, это значило бы, что наружу бы торчали чистые функции, и разные инстансы энкодеров можно было бы дёргать параллельно независимо друг от друга — хоть каждый фрейм параллельно кодируйте. Их и сейчас можно, просто компилятор и типы эту параллельность не гарантируют.
Да (правда, относительно давно). Мне не понравилось что-то в его теории типов — если я правильно помню, там есть 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), который…
Ой-вей, поздравляю с записью по нулевому указателю.
ЗЫ
ЗЫ — чувак написал 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)
Посмотрел бы на этот алготрейдинг, если в моём алготрейдинге на каждый бранч смотрят как на источник вариабельной (и плохой) латентности, а там целый GC/JIT/непонятночто.
И по похожим причинам мой алготрейдинг по факту однопоточный.
Да, вы правы. Я тоже выступаю за сведение налогов в ноль, но пользуюсь всеми налоговыми ништяками и защитами, до которых могу дотянуться, в очень просоциальной европейской стране, в которой живу.
Уже писал в другом треде, но повторюсь: я тоже асоциальный хиккан с нулём общения вне работы, но требовать от окружающих, чтобы они решали мои проблемы, мне в голову не приходит.
Ну хз, я писал на всякие довольно эзотерические темы, фидбек норм. Даже слишком — в какой-то момент я перестал понимать, делаю ли я вещи потому, что они мне нравятся сами по себе, или потому, что мне нравится фидбек от людей.
Неа. Только через других людей вы можете валидировать те вещи, которые завязаны на людей — та самая помощь, или востребованность того, что вы делаете, или умение с людьми общаться, или некоторое подмножество искусства, или тому подобные вещи.
В случае с кодом у вас есть объективный проверяющий: компилятор. Если вы позавчера и вчера не могли решить какую-то задачу, а сегодня смогли (и решение собралось-запустилось), то это объективное улучшение, объективный плюс к самореализации, объективный плюс к вашим скиллам. Если пять лет назад вы писали такой код, что через год ничего непонятно, а год назад писали такой код, что сегодня модифицировать его легче, то это тоже плюс.
Говорить, что без людей невозможно валидировать рост скиллов в программировании (или матане) — это как говорить, что без людей невозможно валидировать рост силы, и что вы теперь жмёте не 10 килограмм, а 20.
И если это и путь в психушку, то только потому, что вы отрываетесь от социально общепринятых паттернов, а не потому, что посмотреть и оценить некому.
Линейные типы завезли уже очень давно.
Надо снова посмотреть, значит, спасибо за напоминание!
Авторы движков за ООП в движке вас сожгут на костре из P4 Prescott. Там топят за data-oriented design (который ООП не то что ортгонален, а даже слегка противоречит), и прочие подобные вещи, которые как раз на ФП ложатся отлично.
Вот характерный доклад.
Отказоустойчивость форматтера — это, конечно, важная тема. А как узнать, что он при этом не сохраняет что-нибудь в БД и не выставляет какие-нибудь флаги третьему процессу, что было добавлено шесть лет назад при попытке починить критический срочный баг в продакшене в три ночи в пятницу вечером?
Тут есть два аспекта.
Во-первых, сама задача не очень богата на бизнес-логику, тестируемую в изоляции от внешнего мира. Условно, писать шелл-скрипты на ФП тоже не то чтобы демонстрирует все его прелести, а тут всё сводится к дёрганью внешних API.
Во-вторых, есть много других способов выражать предметные области. Просто показывать их в таких мелких примерах — плохая идея, потому что читатель увидит «наворотили какой-то хрени, академики, блин» и сделает не совсем верные выводы. В этом проблема с такими мелкими примерами, на самом деле.
На самом деле это тоже проблема, потому что, упрощая,
const— очень грубый признак (и очень врущий, потому что позволяет менять состояние через указатели, например).Почему?
Я недостаточно знаком с энкодерами, поэтому извиняюсь за возможно глупую конкретику, но в общем виде никто не мешает делать (
IOна возвращаемые типы навесьте по вкусу, сути это особо не меняет):Ну или попробуйте накидать такую диаграмму состояний, чтобы там был комбинаторный взрыв, а я попробую с этим побороться.
Исключительно потому, что я дёргаю внешний мир, а сишная либа или дрова не обязаны быть параллельными.
Если бы логика была чисто внутренней, то вся мутабельность бы уже была в
ST, а не вIO, и, упрощая, это значило бы, что наружу бы торчали чистые функции, и разные инстансы энкодеров можно было бы дёргать параллельно независимо друг от друга — хоть каждый фрейм параллельно кодируйте. Их и сейчас можно, просто компилятор и типы эту параллельность не гарантируют.Более того, есть либы вроде https://hackage.haskell.org/package/lvish , которые дают мутабельность, параллельность и детерминизм.
Да (правда, относительно давно). Мне не понравилось что-то в его теории типов — если я правильно помню, там есть
Prop, для которого работает uniqueness of identity proofs (что ломает совместимость с HoTT — один минус), но при этом он не импредикативный (для несовместимой с HoTT теории это минус).Это не те же классы и не те же инстансы. Подробнее я написал ниже здесь и в начале комментария здесь.
Кто запретит? Компилятор?
Какое ещё подключение к базе данных в классе (или функции, неважно), отвечающем за форматирование имени данного ему юзера? Это чтобы макаронный код потом веселее разгребать было?
Так и буду.
На всякий случай, ещё раз подчеркну, что классы — это объявления контрактов/концептов, а инстансы — сообщение компилятору, что данный тип (или множество типов) удовлетворяет контракту, и описание, как именно они это делают. С ООП тут есть некоторое пересечение, но очень отдалённое.
Например, может быть класс и инстансы
где в первой строке
Mult left right resultозначает, что «контракт» описывает отношения трёх типов, а| left, right → resultозначает, что первые два типа автоматически определяют третий единственным образом (и компилятор это проверяет для инстансов и пользуется при тайпчекинге/выводе типов).Я затрудняюсь выразить ровно это на ООП с такой же степенью симметрии.
Или можно (но не нужно) на тайпклассах делать арифметику Пеано, и чтобы компилятор потом проверял, что соответствующие равенства выполняются.
Да. Ну и опять же, это тут в коде выше везде
IO, потому что задача по факту и состоит из одногоIO. На практике даётся больше гарантий, что код (не) делает, за счёт этих самых типов, да.Зависит от задач, конечно, но я ни разу не ловил себя на желании «эх вот бы щас тут ООП-классик бы навернуть».
Зависит от более общего контекста. Можно так, да:
Можно
— то есть, тупо рекорд (структура) с разными функциями, которая может создаваться через
и эта
commonRefможет капчуриться в определения любых функций внутри, и шариться между ними.Это выглядит совсем как ООП, только, опять же, так как это полноценные first-class-citizen-данные, можно легко их собирать из отдельных кусочков и легко модифицировать. Паттерны и языковые фичи вроде миксинов просто становятся не нужны.
Старый, но несложный.
Добавьте оптимизации:
Не могу проверить, потому что хз с чем это запускать вообще.
Но зато ошибки видны просто невооружённым глазом. Например,
вызывает сначала конструктор
sh_ptr<UInt>с нулевым указателемptr, а потом его удаляет через в итогеfixed_alloc::free(ptr), который…, который…
Ой-вей, поздравляю с записью по нулевому указателю.
ЗЫ — чувак написал 1400 строк кода, из которых треть — переизобретение своего наколенного
boost::shared_ptrи прочего подобного, в которых сходу находятся ошибки, и пытается этим опровергнуть наличие проблем с миграцией на новые компиляторы у тех, кого строк кода не 1400, а 140000000.Это, конечно, смешно.
Это про любую технологию сказать можно. Дело всегда в руках, технологии всегда шикарные. Не можешь писать производительный код на JS? Да у тебя просто руки кривые.
Детская травма от необходимости явно писать типы нетривиальных
match-конструкций в определении функций, когда ну вот же оно тут написано прямо в типе функции ну что ты петух тупой что ли блин.За счёт того, что в агде паттерн-матчинг сделан ну вот прям в определении функций, там это всё куда лучше выглядит.
Что, конечно, не отменяет куда более лучшей автоматизации в коке, поэтому имеем то, что имеем.
Он хранится для каждого созданного «экземпляра» fallback-энкодера в замыкании соответствующей функции. Каждый раз, когда я пишу
то у меня «запускается» тело
swFallbackEncoder, которое создаёт (newIORef) мутабельную переменную (isFallback), где лежитFallbackState(CanUseHWпо умолчанию), и возвращает лямбду, которая на каждый фрейм проверяет эту захваченную переменную.На плюсах это выглядело бы примерно как
Как функцию :]
Можно написать тестовый код вроде
и потом
выведет
обратите внимание — trying hw для каждого отдельного энкодера печатается только раз.
Зачем для этого класс? Какой у него стейт? Могу ли я один инстанс класса
DisplayFormatterдёргать из разных тредов? Могу ли я разные инстансы дёргать из разных тредов? Эквивалентен лиDisplayFormatter, отформатировавший сто юзеров, свежесозданному?А можно просто сделать чистую функцию
formatUser : User → String, и там этих вопросов нет. И передавать эти функции как аргументы другим функциям (если очень хочется звучать умно, можно это тоже называть DI).Я другой оратор и у меня другой тривиальный пример, я его ниже написал.
Блин, извините, я идиот, зачем-то флаги держу, когда можно без флагов, и просто в стейте хранить саму функцию, которую надо вызывать. Так гораздо чище, и в кои-то веки пригодился
Control.Monad.mfix: