Control flow as a type
May. 24th, 2024 10:24 amЧем больше я про это думаю, тем больше я его хочу.
Любое замыкание (в расте) может управлять control flow приложения, но только в смысле panic!. Замыкание не может управлять другим control flow, например, break/continue/return.
Это делает не экивалентным цикл итератора и применение map для того же самого итератора. Несправедливо! Если бы только компилятор понимал управление control flow и мог правильно проверить его совместимость...
Любое замыкание (в расте) может управлять control flow приложения, но только в смысле panic!. Замыкание не может управлять другим control flow, например, break/continue/return.
Это делает не экивалентным цикл итератора и применение map для того же самого итератора. Несправедливо! Если бы только компилятор понимал управление control flow и мог правильно проверить его совместимость...
no subject
Date: 2024-05-24 08:28 am (UTC)no subject
Date: 2024-05-24 08:46 am (UTC)also, хочу узрить return return на монадках.
no subject
Date: 2024-05-24 09:01 am (UTC)Опишите, что он делает.
no subject
Date: 2024-05-24 09:06 am (UTC)Теперь попробуй это записать через filter/map:
no subject
Date: 2024-05-24 09:53 am (UTC)module Test where
import Control.Monad
testFn rc bc dv l = either id id $ (forM_ l $ \e -> if rc e then Left e else (if bc e then Left dv else return () ) ) >> return dv
*Test> testFn (==1) (==2) 0 [3,3,2,1]
0
*Test> testFn (==1) (==2) 0 [3,3,1,2]
1
no subject
Date: 2024-05-24 09:54 am (UTC)no subject
Date: 2024-05-24 10:58 am (UTC)Напиши без материализованного энума (без байтов в памяти) и в виде отдельной функции, которую ты вызываешь по имени.
Я плохо читаю хаскель и не могу понять, if выполняется внутри лябмды или снаружи.
no subject
Date: 2024-05-24 12:30 pm (UTC)Не обязательно к хаскелю, э. Скала тоже хорошая.
no subject
Date: 2024-05-24 01:31 pm (UTC)Отчасти. инстанс монадки Either (эквивалент Result в Rust) написан так, что после первого Left ничего не вычисляется, там даже список не будет конструироваться. Кроме того, GHC умный, вполше может промежуточное построение Either optimize away.
>Я плохо читаю хаскель и не могу понять, if выполняется внутри лябмды или снаружи.
Внутри. Все if внутри лямбны, снаружи только монадка.
> Напиши без материализованного энума (без байтов в памяти) и в виде отдельной функции, которую ты вызываешь по имени.
Какие мы привередливые.
import Control.Monad.Cont testCn :: (a -> Bool) -> (a -> Bool) -> a -> [a] -> a -- ^ immidiate return condition checker, abort and return default value checker, default value, list to proc. testCn rc bc dv l = flip runCont id $ callCC $ \rexit -> do callCC $ \lexit -> forM_ l $ \e -> do when (rc e) $ rexit e when (bc e) $ lexit () return dvИспользуется монада продолжений, на которой можно сочинить любой разумный control flow. Первый callCC дает продолжение rexit (return-exit), которое позволяет выскочить из функции сразу, второй callCC дает продолжение, которое выскакивает перед
no subject
Date: 2024-05-24 01:31 pm (UTC)no subject
Date: 2024-05-24 01:56 pm (UTC)Ну где-то да. At a certain degree.
no subject
Date: 2024-05-24 04:27 pm (UTC)Семантика цикла в верхнем примере — поиск с ранним выходом при успехе и ранним осознанием облома. Соответственно, его надо выражать не в базисе map/filter, а в базисе map/filter/take 1.
Не тестировал, но что-то такое:
no subject
Date: 2024-05-25 05:25 am (UTC)Вообще мысли вида «хочу иметь
break/continue/returnexpr/throwexpr в виде first-class значения» обычно приходят при попытке применить рефакторинг Extract Method к куску говнокода, где на входе коллекция, потом по нейforвыше человеческого роста, а внутри этогоfor’а — вышеупомянутые управляющие конструкции в ассортименте.no subject
Date: 2024-05-27 09:22 am (UTC)no subject
Date: 2024-05-27 09:23 am (UTC)no subject
Date: 2024-05-27 09:28 am (UTC)Хочется эти ответы и проверки вынести в отдельные функции.
Например, представь себе, обычный for-цикл по коллекции (без права на спекулятивное чтение, то есть ты не знаешь длины итератора и не можешь делать peek).
Теперь у тебя есть пять решений: break, continue, return (math for(i)), update context, pass.
Каждое i должно быть обработано каждой из 10000 функций KYC/AML подразделения, которое делает всякую фигню (включая запрос фотки задницы на фоне задней страницы паспорта).
Как ты такое напишешь? Разумеется, ты сделаешь рефакторинг, чтобы тебе такое не надо было писать. Но рефакторинг ты сделаешь, потому что тебе инструмент не позволяет.
no subject
Date: 2024-05-27 10:04 am (UTC)А характерный размер коллекции (из которой каждое i)?
Потому что при таком размере внутреннего цикла я буду серьёзно задумываться, что, может, нам нужно не гнездо из двух вложенных циклов и не двухэтажный мап-мап-фильтер, а какой-то дейта-пайплайн о десяти тысячах и двух вершинах. возможно распределённый.
no subject
Date: 2024-05-27 10:04 am (UTC)testCn rc bc dv l = flip runCont id $ callCC $ \rexit -> do { callCC $ \lexit -> forM_ l $ \e -> do{ when (rc e) $ rexit e; when (bc e) $ lexit (); }; return dv; }runCont - раскрывает монадку продолжения
callCC - расставляет точки возврата (куда выходить) и отправляет в свой аргуменет условную 'команду выхода' в эту точку возврата.
forM_ - запустить монадические вычисления на 'iterable' коллекции, и склеить 'слева направо', выкидывая результат. Он получает здесь лямбду, в которую передает элемент. Эта внутренняя лямбда синтаксически захватывает 'команды выхода' из более внешних.
Если тебя смущает $, то это чтоб скобки не размножались. f $ g == f (g) . Удобно, когда нужно отграничить лямбду или сделать длинный пайплайн.
no subject
Date: 2024-05-27 10:07 am (UTC)Если коду было позволено разрастись до более чем трёх-пяти требований в одном цикле и тот рефакторинг, который ты описываешь, ещё не сделан — это говнокод.
no subject
Date: 2024-05-29 10:47 am (UTC)no subject
Date: 2024-05-29 12:15 pm (UTC)Ну и? Надо сесть за моделирование и тип возврата этих функций-требований сформулировать в терминах предметной области («этому разрешить без очереди и без дальнейших проверок», «да, если не противоречит другим проверкам», «минус десять очков Гриффиндору», «ни за что и никогда»), а не в терминах управляющих конструкций того цикла, в котором перебираются проверки.
Если проверки одного клиента влияют на результаты проверок для других (например, у нас ограниченный ресурс), то, значит, у нас опять не задача на map-map-filter, а на map-map-sum-sort-take 100.