Comments 34
Если язык позволяет писать "правильно" (чтобы вы в это не вкладывали) пишите правильно. Прикручивать некий рантайм и библиотеку чтобы добиться "правильности" - в первый момент звучит как излишне.
пишите правильно
так не работает. у всех разработчиков разный уровень знаний и не получится всех заставить писать "правильно".
Как показывает практика, что действительно получается, так это использовать компиляторы а не интерпретировать выполнение языков на лету. Компилятор работает на уровне написания кода.
Наглядный пример это TypeScript, который вселил вторую жизнь в JavaScript экосистему и теперь заставляет писать разработчиков хотябы без синтаксических ошибок и неправильного использования типов.
Так же с эффектами. Роль рантайма эффектов такая же важная как и компилятора, эффекты позволяют на уровне компилятора не забывать обрабатывать ошибки и подставлять используемые зависимости.
По-моему время показало, что ФП в джаве (и котлине) ровно столько, сколько нужно.
Я ни раз заглядывался на либу Arrow-kt, которая тоже вся из себя про православный ФП и эффекты - но я бы посмотрел на реальный большой проект полностью на ЭТОМ. Допускаю, что я просто тупой и не шарю, но даже простые сниппеты это малочитаемый оверинжиниринг. Высоковата цена за сомнительное повышение надежности. А еще забавный факт, что за свою историю либа умудрялась пару раз радикально перепахивать API и терять обратную совместимость.
Идея использования эффектов не привязана к конкретному языку программирования.
Если язык не поддерживает такой подход (как к примеру Haskell/Rust) то вы получаете накладные расходы по памяти и производительности на ровном месте, плюс из-за отсутствия синтаксической поддержки, это почти всегда выглядит многосложно громоздко, не понятно и неестественно.
Из моего опыта работы с различными языками и парадигмами я пришёл к выводу, что ООП и ФП отлично дополняют друг друга. При использовании каждой из этих парадигм по отдельности можно столкнуться либо с избыточным "шумным" кодом, насыщенным абстракциями и наследованием, либо с кодом, понимание которого требует глубоких математических знаний.
В эффектах работа с ошибками становится прозрачной и естественной:
Подход, ориентированный на эффекты, позволяет разрабатывать приложения любой сложности без увеличения когнитивной нагрузки на код.
Вместо тысячи слов.
Код без эффектов:
contexMsg = "test implementation"
function heyEffect(arg: number) {
if (arg == 2) throw Error("boom"));
console.info(contexMsg, "hey")
return true;
}
heyEffect(1),
Код с эффектами:
class MyConsole
extends Context.Tag("MyConsole")<MyConsole, {
log: (input: unknown) => void
}>(){}
// ManagedRuntime.ManagedRuntime<MyConsole, never>
const myRuntime =
ManagedRuntime.make(Layer.succeed(MyConsole, {
log: (input) => console.info("test implementation", input)
}))
function heyEffect(arg: number) {
return Effect.gen(function*() {
const logger = yield* MyConsole;
if (arg == 2) yield* Effect.fail(Error("boom"));
logger.log("hey")
return true;
});
}
pipe(
heyEffect(1),
Effect.provide(myRuntime), // в Runtime уже есть сервис MyConsole
Effect.runSync //запуск эффекта
);
Можно оценить все прелести такой реализации.
Эффекты это хорошо, но только если есть поддержка на уровне самого языка.
Попытки любым способом затянуть то что понравилось в свой язык программирования, который это не особо поддерживает, приводит к таким монструозным портянками, снижению производительности и увеличению расходов на память.
Вместо тысячи слов.
...
Можно оценить все прелести такой реализации.
Первый пример кода, без эффектов, небезопасный к выполнению и плохо тестируемый. Просто черный ящик.
Этот код естественно меньше, потому что мы "забили" на то, что у вызывающей стороны будут проблемы.
Второй пример кода, безопасный, потому что видим описание того, что этот код может кинуть ошибку и что он использует.
Когда этот код будут вызывать, то вызывающая сторона может обрабатывать ошибки и подставлять разные реализации того, что использует код.
Естественно быстрее и проще написать небезопасный код, но у этого есть цена. Эффекты дают возможность писать надежный код когда это необходимо.
Я примеры эти привел для того чтобы было проще понять идею. А вы, похоже, идею не рассматриваете а подбираете инструменты по типу "тут меньше букв, значит беру это".
Если язык не поддерживает такой подход (как к примеру Haskell/Rust) то вы получаете накладные расходы по памяти и производительности на ровном месте, плюс из-за отсутствия синтаксической поддержки, это почти всегда выглядит многосложно громоздко, не понятно и неестественно.
Попытки любым способом затянуть то что понравилось в свой язык программирования, который это не особо поддерживает, приводит к таким монструозным портянками, снижению производительности и увеличению расходов на память.
Вот специально для таких комментариев я посветил пару абзацев в статье (Использование эффектов делает программы медленнее).
Вы уж определитесь что вы хотите, меньше "монструозных портянок" или быстрое исполнение кода с минимумом памяти.
Первый пример кода, без эффектов, небезопасный к выполнению и плохо тестируемый. Просто черный ящик.
Тест1: передаем 2, проверяем бросит ли исключение.
Тест2: передаем любое число кроме 2, проверяем вернет ли true.
Функция полностью протестирована.
Я примеры эти привел для того чтобы было проще понять идею. А вы, похоже, идею не рассматриваете а подбираете инструменты по типу "тут меньше букв, значит беру это".
Идею я всецело поддерживаю, но не реализацию, когда полезный код тонет в бойлерплейте вспомогательного. Если брать типичную коммерческую разработку, у меня итак количество бизнес кода за горизонт уходит, мне хочется максимально все упростить и убрать лишний шум чтобы ориентироваться в этом бесконечном потоке логики а Вы предлагаете это все еще сверху обмазать еще одним толстым слоем вспомогательного бойлерплейта который еще и работает абсолютно не явно. Это банально тяжело читать и от этого быстро устаешь а значит и ошибиться шансов будет больше. Да в одних местах сложнее будет ошибиться а вот в бизнес логике, от которой эффекты не защищают, человек уставший читать такие портянки намного быстрее ошибется.
Вы уж определитесь что вы хотите, меньше "монструозных портянок" или быстрое исполнение кода с минимумом памяти.
Я хочу чтобы код был быстрый, понятный и надежный. Мне не интересно менять шило на мыло решая одну проблему подменять ее другой. Эффекты в TS это типичный пример того что решая проблему с надежностью мы платим нечитаемым медленным кодом с высокой когнитивной нагрузкой, который все равно не гарантирует тотальной надежности. Нужен язык со встроенными возможностями, чтобы я писал код плюс минус как раньше с парочкой ключевых слов а не это.
То что вы описываете это полный аналог RX - ради асинхронного исполнения, изуродуем весь код операторами и комбинаторами с кучей плохо читаемого бойлерплейта.
Затем сделали корутины - когда все тоже самое но код почти не отличается от обычного или вообще не отличается а плюсы остались.
Замечательный пример Rust в котором мне ну нужно выбирать красиво написать в функциональном стиле или быстро в императивном, оба варианта будут одинаковы по скорости и памяти. Вот это хороший вариант а не менять шило на мыло.
Тест1: передаем 2, проверяем бросит ли исключение.
Тест2: передаем любое число кроме 2, проверяем вернет ли true.
Функция полностью протестирована.
А почему вы тогда код этих тестов не написали когда приводили пример кода из статьи? Зачем вы вообще пишете такие тесты если вам нужно быстро написать код и задеплоить?
Использование эффектов избавляет от написание подобных тестов.
а Вы предлагаете это все еще сверху обмазать еще одним толстым слоем вспомогательного бойлерплейта который еще и работает абсолютно не явно
Я не понимаю про какой слой бойлерплейта вы говорите. Я приводил пример из Effect-ts чтобы было понятно как это работает.
Нужен язык со встроенными возможностями, чтобы я писал код плюс минус как раньше с парочкой ключевых слов а не это.
Языки развиваются долго, годами, потому что новые фичи проходят несколько этапов от draft версии до in progress.
JS когда-то поддерживал только callback, не было async/await. Сейчас можно использовать await но у него другая проблема теперь, разработчики не использую try catch и пишут небезопасный код.
Я бы тоже хотел язык, который позволил бы мне писать с преимуществами, которые дают эффекты.
Замечательный пример Rust в котором мне ну нужно выбирать красиво написать в функциональном стиле или быстро в императивном, оба варианта будут одинаковы по скорости и памяти. Вот это хороший вариант а не менять шило на мыло.
У меня нет большой практической экспертизы в Rust, но знаю что он для системного программирования. Я любой язык назову прекрасным, если у него функции будут возвращать Result тип. А еще у него все переменные по умолчанию неизменяемые, есть pattern matching, круто!
Да, он клевый и он активно развивается, но он не для прикладного программирования, где есть сборщик мусора, как минимум.
Использование эффектов избавляет от написание подобных тестов.
Нет. Вы будете писать такие такие же тесты хоть с эффектами хоть без. Наличие эффектов не гарантирует что вы их правильно описали логику.
Я бы тоже хотел язык, который позволил бы мне писать с преимуществами, которые дают эффекты.
Haskell?
Я не понимаю про какой слой бойлерплейта вы говорите. Я приводил пример из Effect-ts чтобы было понятно как это работает.
Из Ваших же примеров. Когда мне нужно написать много дополнительного кода только потому что это самопально-библиотечное а не нативное решение поддерживаемое языком.
Ярчайший пример это имплементация ФП, как это элегантно и минималистично смотриться в Haskell/F# или на худой конец Scala и в какой плохо читаемый бойлерплейт превращается в JS/TS/Java/Kotlin. Потому что первые языки имеют дизайн поддерживающий это а вторые нет. Если есть нужда в эффектах нужно использовать инструмент который для этого подходит.
Haskell?
Я имел ввиду язык, который в топе 10-15 языков. Haskell чистый ФП язык который использует узкий круг компаний
Наличие эффектов не гарантирует что вы их правильно описали логику.
Эффекты гарантируют что вы ошибки все обработали и передали правильный контекст
Из Ваших же примеров. Когда мне нужно написать много дополнительного кода только потому что это самопально-библиотечное а не нативное решение поддерживаемое языком.
Это самопально-библиотечное скачивается по 2 миллиона раз в неделю

Ярчайший пример это имплементация ФП, как это элегантно и минималистично смотриться в Haskell/F# или на худой конец Scala и в какой плохо читаемый бойлерплейт превращается в JS/TS/Java/Kotlin.
Я не буду с вами спорить и не понимаю предмет того, что в чем вы хотите меня убедить. У вас такой большой опыт в языках, и Rust и Haskel и плохая Scala, и в Котлин все очень плохо с бойлерплейтом. Спасибо за активность
Эффекты гарантируют что вы ошибки все обработали и передали правильный контекст
Разраб ошибется в условии и выбросить ошибку там где значение нормальное что с эффектами что без, тесты на это писать нужно что с эффектами что без.
Это самопально-библиотечное скачивается по 2 миллиона раз в неделю
Да хоть 10 миллионов, что дальше то? Если это будут скачивать часто у меня кровь из глаз перестанет идти от просмотра такого кода?
Я не буду с вами спорить и не понимаю предмет того, что в чем вы хотите меня убедить. У вас такой большой опыт в языках, и Rust и Haskel и плохая Scala, и в Котлин все очень плохо с бойлерплейтом.
Вроде бы пишу на русском языке. Scala хоть и дает хорошую поддержку ФП в отличии от многих других языков, все равно слишком распухший и не консистентный яп в котором одной и тоже можно сделать 10 способами и в котором сложности ФП перепутались со всеми сложностями ООП, но это на любителя. Котлин не бойлерплейт, но попытка притащить в него ФП в виде библиотеки Arrow делает из кода черти что, в отличии от той же Scala где это хотя бы заложено. На одной из конференций, архитектор языка Андрей Бреслав прямо сказал что Kotlin это не ФП язык и таким не будет. Тащить фп в языки которые это не поддерживают, в которых нет поддержки компилятора и синтаксиса - значит получить кашу из бойлерплейта в коде как в Ваших примерах. В Scala это есть, хоть мне и не нравится дизайн языка, а в TS - нет.
Вы пробовали писать именно приложения на haskell и zio/effect-ts? Если нет, то возможно вы не совсем понимаете концепцию продуктивной системы эффектов.
Основополагающим для неё является вывод возвращаемого типа эффекта параметризованного всеми задействованными в do-нотации ошибками и зависимостями, которого в haskell нет (буду рад узнать что есть какое-то расширения языка которое я упустил).
Два факта позволяют получить заметное улучшение опыта разработки за счёт использования эффектов: возможность посмотреть выведенный тайпхинт в котором на виду пояснены все ошибки и зависимости (а не скрыты за каким-то номинальным типом), и возможность не писать самому этих тайпхинтов повсюду.
Я и мои коллеги приняли решение что эта возможность видеть ошибки и зависимости в типах стоит каждой строчки необходимого бойлерплейта.
Стоит еще добавить что помимо безопасности, система эффектов как и любая монада/монадтрансформер добавляет целый мета-уровень в исполнение программы. Что позволяет например получать практически бесплатные трассировки запросов которые показывают каждую вызванную функцию и все тайминги. В ZIO вообще без единой строчки кода (за счет имплиситов scala), в Effect-ts нужно оборачивать объявленные функции в Effect.fn (с чем тоже можно смириться, если посмотреть на другие решения для трейсинга в экосистеме ts и сравнить)
А что в Вашем понимании прикладное программирование? Думаю, что почти на любую задачу будет пример использования rust в таком контексте. UI, backend, desktop, mobile, game dev, cli utils + cli ui, db, fs, OS. Что такого нельзя написать на rust, что он вдруг попал в категорию неприкладных. Наличие gc такой себе показатель. Какая разница почистит переменную gc или автоматом при выходе из scope?
Согласен с вами что на Rust можно делать много чего. На мой взгляд, он сложнее чем Java/TypeScript, нужно понимать значение указателей, как выделять память под структуры, не бояться макросов и тп. В языках с GC как то попроще.
Какая разница почистит переменную gc или автоматом при выходе из scope?
вы правы
Вы на полном серьезе считаете что вот это(ваши эффекты):
import { Context, Effect, pipe } from "effect";
class MyConsole
extends Context.Tag("MyConsole")<MyConsole, {
log: (input: unknown) => void
}>(){}
//function heyEffect(arg: number): Effect.Effect<boolean, Error, MyConsole>
function heyEffect(arg: number) {
return Effect.gen(function*() {
const logger = yield* MyConsole;
if (arg == 2) yield* Effect.fail(Error("boom"));
logger.log(logger)
return true;
});
}
pipe(
heyEffect(1),
Effect.provideService(MyConsole, {
log: (input) => console.log(input)
}),
Effect.catchAll(error => { // error соответсвует только типу Error
console.log("hey was failed with expected error", error.message);
return Effect.void // нужно вернуть новый эффект
}),
Effect.runSync //запуск эффекта
)
Лучше чем вот это(подход здорового человека)?:
function hey(arg: number) {
if (arg == 2) throw Error("boom")
console.log("hey", arg);
}
try {
hey(1)
} catch (e) {
const error = e as Error;
console.log("hey was failed with expected error", error.message);
throw error; // Если дальше нет смысла продолжать
}
Более того, далеко не всегда нужно обрабатывать ошибки на каждом участке, как минимум потому что это превратит код в месиво. Поэтому в 99% случаев обрабатываются только ошибки от АПИ и то, если на них есть специальная логика, в остальных случаев они ловятся через общий обработчик и показывают плашку с текстом ошибки от АПИ || "Что-то пошло не так". А код мы пишем так, что он работал без ошибок. Ну по крайней мере я пишу код, который работает без ошибок в 99.999% случаев, на все остальное глобальный хэндлер ошибок, который сигнализируют пользователю что ошибка в принципе произошла хватает за глаза. Зато код максимально приятный и чистый. Вообще вы не тот язык выбрали, идите в Go, там после каждой строчки пишут
if err != nil {
return err // или все что годно
}
И я не шучу, буквально после вызова каждой функции. Вот для вас там будет рай.
А срать в коммерческие проекты вашим противоестественным для JS/TS кодом и подходом не надо. Подумайте о людях, которые туда придут когда вы уволитесь или вас уволят(что было бы логично, увидя что вы пишете такой код).
Это самопально-библиотечное скачивается по 2 миллиона раз в неделю
Знаете что делают миллиарды мух каждый день? Значит по вашей "логике", нужно делать тоже самое.
Почему вы такое хамло?
Я постарался поделиться знаниями, привести примеры что был предмет обсуждение, а Вы приходите сюда и пишете столько негатива в мой адрес, что меня нужно уволить, сравниваете с мухами людей?
Желаю сил людям, которые с вами работают. На ваши комментарии отвечать не буду
Почему вы такое хамло?
Если ваш код и подход критикуют, то всё? Такого просто быть не может?
Я постарался поделиться знаниями, привести примеры что был предмет обсуждение
Ну так вот я и дал комментарий по этому предмету.
что меня нужно уволить
Я дал свою оценку с точки зрения здравого смысла, такой код не должен быть в коммерческий проектах где используется JS/TS, эти языки и их подходы вообще даже близко не про такое. Если человек пишет чужеродный код, то его либо он перестает это делать, либо его нужно уволить. Ничего тут такого нет, стандартная практика.
сравниваете с мухами людей?
Какая разница? Ведь это просто ваша "логика", если кто-то что-то делает и их количество измеряется не единицами, значит и мы должны. Не хотите про мух, вот вам про людей:
Знаете что делают миллионы наркоманов каждый день? Знаете что делают миллионы алкоголиков каждый день? Ну я думаю вы поняли абсурдность такого рода ваших "аргументов" про кол-во скачиваний из npm.
Желаю сил людям, которые с вами работают.
Серьезно?) Вы видели как я пишут?) Всё всегда всем максимально понятно и наглядно. А теперь посмотрите как вы его пишете. Write only + очень грязно.
На ваши комментарии отвечать не буду
Детский сад, не дали игрушку по первому требованию.
Могу дать свой фидбек.
Сразу уточняю. Не хочу никого обидеть оскорбить. Если такое произошло, дайте цитату и буду исправляться.
Функция — это термин, используемый как в ООП, так и в ФП. Поэтому я буду использовать термин "ООП-функция", чтобы обозначать классическую функцию, а не математическую.
Сразу вопрос по форме, а где в вашей статье далее будут ситуации, когда можно перепутать о какой функции идет речь? Вы пишете статью про языки программирования. Программисты не говорят "ООП-функция", мы говорим просто "функция". Поэтому вопрос, это было зачем?
"Функциональщики" разделяют функции на предсказуемые и грязные. Они знают, что такое побочные эффекты, и это знание позволяет им писать надежные программы.
Вопрос по форме, вы имеете в виду, что это "знание" помогает только "функцинальщикам" или мне оно тоже поможет? Или даже так, а не поможет ли это знание вообще любому "абстрактному" программисту?
Это я все к тому, что, по моему, вы выбрали крайне странную форму, что бы просто рассказать о пользе чистых функций.
В языках программирования, ориентированных на ООП, функцию часто рассматривают как именованный фрагмент кода, который можно просто запустить.
Куда запустить? Как вы себе это представляете? Я не очень понял, что вы имеете в виду. Вот есть программа, которую, да, запускают. А есть код программы. Функции позволяют обращаться к коду специальным образом описанному в одном месте из другого места программы. А еще есть сущности времени выполнения, но вы ведь не это имеете в виду?
Игнорирование особенностей функций в таких языках нередко приводит к хаосу.
Каких особенностей? Почему явно не написать, что игнорирование побочных эффектов в функциях создает известные проблемы. Потому что проблемы действительно известные. Ну, можете назвать парочку. А то у вас получается:
Некоторые разработчики осознают преимущества предсказуемых функций и используют это понимание для создания качественного кода (например, для упрощения тестирования). Однако особенности дизайна ООП-языков скрывают эти детали от менее опытных специалистов.
При этом вы уже выше написали:
"Функциональщики" разделяют функции на предсказуемые и грязные. Они знают, что такое побочные эффекты, и это знание позволяет им писать надежные программы.
Т.е. и "функциональщики в теме" и "некоторые разработчики" тоже. Кстати, а что значит "некоторые разработчки"? Например, в экосистеме реакта уже лет 10 есть ясное понимание какие нужны функции для разных задач. Там до хрена разработчиков.
Вот вы назвали раздел "Проблема ООП-функции". Ввели это обозначение, а потом... а где проблема? Где-то кто-то что-то проигнорировал, ООП-язык что-то скрыл. Вот у меня такое впечатление сложилось.
Согласен, вполне возможно, что только у меня.
Функция имеет название, аргументы и, возможно, тип возвращаемого значения.
У меня сложилось впечатление, что вы решили изобрести велосипед на любой термин. Далее, вы будете использовать ТС. А почему в принципе не взять его за основу и на его примере не объяснять ваши мысли?
В данном случае, если вы пишете просто "аргументы", то и про возвращаемое значение так же можно просто писать "возвращаемое значение". А то, если по чесноку, писать тогда нужно было так:
"Функция возможно имеет название, возможно имеет аргументы, возможно имеет возращаемое значение".
Тело функции – это код, который выполняется при её вызове. При этом в теле может находиться любой код, который даже не отражает название функции.
Вода же, нет?
По сигнатуре функции hey нельзя понять ...
А почему ясно не дать определение сигнатуры функции? Дальше будет видно, что это необходимо.
Представим, что функцию hey запустили. В этот момент в неё неявно передается контекст выполнения, содержащий глобальный объект console. Это делает невозможным подставить разные реализации сервиса console для разных запусков функции hey.
Мы сейчас говорим про JS? Если про JS то никаких проблем поменять реализацию console для любого вызова hey у нас не будет. Причем данное свойство широко используется в разработке. За примерами далеко ходить не надо. Например, есть MSW, который позволяет мокать fetch в браузере, тестах.
Скорее всего, вы имеете в виду качественное проектирование при котором у программной единицы должна быть ярковыраженная семантика. Иными словами, по моему, вы выбрали не очень выразительную форму что бы донести данную мысль.
```
function makeHeyEffect(arg: number) {
return Effect.gen(function*() {
const logger = yield* MyConsole;
if (arg == 2) yield* Effect.fail(Error("boom"));
logger.log(logger)
return true;
});
}
```
Теперь по сигнатуре функции видно, что heyEffect создает объект класса Effect. В generic параметрах эффекта указано три типа:
Вот тут хочется вспонить, что такое сигнатура функции. Это использованные при объявлении имя функции, имена и типы параметров, и тип возвращаемого значения.
А теперь сравнивамем. Было function hey(arg: number) {
стало function makeHeyEffect(arg: number) {
. Конечно, где-то под капотом ТС вывел тип функции, но при объявлении вы этого не написали. Комментарий - это не часть сигнатуры.
И сразу вопрос, а что вы хотели всем этим показать? Если вы хотели, что бы у вас была сущность с зависимостью от MyConsole, и возращала объект ошибки, то почему вы этого не сделали в первом примере? Потому, что по смыслу оба примера должна передавать решение одной и той же задачи.
А если я правильно понял, задача была:
1. Обозначить зависимость от MyConsole
2. Возвращать из функции объект ошибки.
По моему, вы подменили понятия. И в первом случае была одна задача, а во втором уже совсем другая задача, поэтому они так сильно и отличаются.
И если с обработкой ошибок еще туда сюда, то почему вы так старнно обошли работу с MyConsole в певрмом примере не ясно.
Если вы хотели сказать, что эффекты навязывают определенные вещи, то в случае с MyConsole это не так. Потому что желание вынести логирование в отдельный сервис - это следует из целей проектирования. Если такие цели не поставить, можно написать эффект, который будет писать в консоль явно
Я дальше уже глазами просто по теме пробежался. Все это очень интересно и полезно. Но, по моему, вы зря пытаетесь изобретать велосипеды и придумывать объяснения и определения, которые уже даны и являются известными.
Спасибо за констуктивный фидбек, постраюсь ответить на часть ваших вопросов
Вы пишете статью про языки программирования. Программисты не говорят "ООП-функция", мы говорим просто "функция"
Я не писал статью про ЯП, а про эффекты. Я упоминаю ФП и ООП потому что эффект это симбиоз двух парадигм.
Программисты не говорят так, да. Я сказал про это в начале статьи что имеется ввиду под этим термином. Вероятно, это была не лучшая идея.
Сразу вопрос по форме, а где в вашей статье далее будут ситуации, когда можно перепутать о какой функции идет речь?
В статье я сравниваю обычную функцию/метод с эффектом. Чтобы не писать "классическая функция/метод класса".
Вопрос по форме, вы имеете в виду, что это "знание" помогает только "функцинальщикам" или мне оно тоже поможет? Или даже так, а не поможет ли это знание вообще любому "абстрактному" программисту?
Это знание поможет любому абстрактному программисту.
Куда запустить? Как вы себе это представляете? Я не очень понял, что вы имеете в виду
Когда программа запускается, то начинается выполнение с ее главной функции. Эта функция запускает/выполняет/передает управление другой функции и так далее.
Функция это именованый фрагмент кода, которому передается управление в момент вызова.
Это я все к тому, что, по моему, вы выбрали крайне странную форму, что бы просто рассказать о пользе чистых функций.
Статья не про пользу чистых функций, а про эффект, который является чистой функцией.
Вот вы назвали раздел "Проблема ООП-функции". Ввели это обозначение, а потом... а где проблема? Где-то кто-то что-то проигнорировал, ООП-язык что-то скрыл. Вот у меня такое впечатление сложилось.
Да, проблема в том, что классическая функция скрывает информацию, такие как перечень возможных ошибок выполнения. Возможно, для меня такое повествование проблемы показалось понятным, для других читателей нет
Мы сейчас говорим про JS? Если про JS то никаких проблем поменять реализацию console для любого вызова hey у нас не будет. Причем данное свойство широко используется в разработке. За примерами далеко ходить не надо. Например, есть MSW, который позволяет мокать fetch в браузере, тестах.
Да, можно изменить глобальный контекст а потом запустить код.
По моему, вы подменили понятия. И в первом случае была одна задача, а во втором уже совсем другая задача, поэтому они так сильно и отличаются.
И если с обработкой ошибок еще туда сюда, то почему вы так старнно обошли работу с MyConsole в певрмом примере не ясно.
Вы абсолютно правы, это моя ошибка что примеры немного разные. Нужно было сервис пробрасывать аргументом функции, в первом примере (который без эффектов).
Это привело к тому, что кода с эффектами стало чуть больше.
Скорее всего, вы имеете в виду качественное проектирование при котором у программной единицы должна быть ярковыраженная семантика. Иными словами, по моему, вы выбрали не очень выразительную форму что бы донести данную мысль.
Я не знал как лучше донести эту мысль, остановился на варианте который оформил в статью
Я дальше уже глазами просто по теме пробежался. Все это очень интересно и полезно. Но, по моему, вы зря пытаетесь изобретать велосипеды и придумывать объяснения и определения, которые уже даны и являются известными.
Я хотел объяснить, какая проблема есть и почему, что такое эффекты и как они решают эту проблему.
В статье я сравниваю обычную функцию/метод с эффектом. Чтобы не писать "классическая функция/метод класса".
Так и не надо писать этого. Для выражения мыслей вы используете ТС. В ТС все это просто функции. Вы подумайте, вы уже выбрали инструмент для своих мыслей. Поэтому выражать в терминах этого инструмента - это нормально.
Это знание поможет любому абстрактному программисту.
Вот и я так подумал. А вы эту мысль мусолите в нескольких абзацах, рассказывая про разных людей.
Когда программа запускается, то начинается выполнение с ее главной функции. Эта функция запускает/выполняет/передает управление другой функции и так далее.
Вот я про это и говорю. Можно просто открыть доку на ТС и взять от туда абзац про функции и тогда, часть текста просто будет не нужна.
Функция это именованый фрагмент кода, которому передается управление в момент вызова.
Ну вот опять те же грабли. Функция - это базовый строительный блок любого приложения, будь то локальная функция, импортированная из модуля или метод класса. Далее можно дать определение сигнатуры функции, что бы понимать чем одна отличается от другой.
Вы же именно с этой стороны рассматриваете функцию? Т.е. как базовый строительный блок.
Статья не про пользу чистых функций, а про эффект, который является чистой функцией.
Статья нет, а вот абзацы да.
Да, проблема в том, что классическая функция скрывает информацию, такие как перечень возможных ошибок выполнения. Возможно, для меня такое повествование проблемы показалось понятным, для других читателей нет
Ну, вот смотрите. Что значит функция скрывает информацию о возможных ошибках исполнения? Например, вы понимаете, что не перехваченное исключение на то и не перехваченное исключение, потому что его не перехватили. Любая ошибка синтаксиса вызовет такое исключение.
Скорее всего вы имеете в виду поведение программы обусловленное реализуемым бизнес процессом. Тогда ошибки представляют из себя определенные ситуации. Учет этих ситуаций - это задача проектирования. И функция все эти ситуации может обрабатывать, что будет отражаться на перечне выходных значений. Иными словами, сама по себе функция не скрывает информацию об ошибочных ситуациях. Это разработчик может посчитать, что ситуация не достойна внимания.
Вам ничто не мешает так же перехватывать исключения внутри функции и делать возвращаемое значение более богатым. Эффекты именно так и поступают.
Да, можно изменить глобальный контекст а потом запустить код.
Если мы говорим про js, это можно делать на лету.
Я не знал как лучше донести эту мысль, остановился на варианте который оформил в статью
В таких случаях, я бы брал реальную задачу из реального проекта, а не hello world. И формулировал именно задачу. А вы сначала написали код, а уже по нему сформулировали задачу. Поэтому у вас две разных задачи из двух примеров кода.
Я хотел объяснить, какая проблема есть и почему, что такое эффекты и как они решают эту проблему.
Вот если посмотреть на вводную статью Why effect? Там огромногое количепство воды и основной посыл, что эффекты имеют мощную богатую экосистему для решения различных задач.
Вы захватили DI и альтернативу исключениям в искусственном примере. У разрабов эффектов был естественный пример, а его реализация на эффектах имела минимум обслуживающего кода.
Иными словами, стоит сначала сформулировать задачу, которую хотите решить и дать решение на эффектах и без них.
Так и не надо писать этого. Для выражения мыслей вы используете ТС. В ТС все это просто функции. Вы подумайте, вы уже выбрали инструмент для своих мыслей. Поэтому выражать в терминах этого инструмента - это нормально.
Вероятно вы правы, в статье я не хотел делать упор на конкретный ЯП, поэтому TS прятал в спойлеры. Но, похоже, что я сам себе усложнил задачу и сам себя обманывал, потому что в статье был код на TS в любом случае.
Вы же именно с этой стороны рассматриваете функцию? Т.е. как базовый строительный блок.
Да, строительный блок.
Вот если посмотреть на вводную статью Why effect? Там огромногое количепство воды и основной посыл, что эффекты имеют мощную богатую экосистему для решения различных задач.
Да, поэтому я хотел раскрыть идею эффектов а не рассказывать сразу про экосистему. У Effect-ts классная точечная документация, но нужно пройтись по отдельным главам, чтобы пазлы сложились в голове.
Иными словами, стоит сначала сформулировать задачу, которую хотите решить и дать решение на эффектах и без них.
Я согласен, нужно было придумать хороший пример, и решать его обычным способом и через эффекты.
Скорее всего вы имеете в виду поведение программы обусловленное реализуемым бизнес процессом. Тогда ошибки представляют из себя определенные ситуации. Учет этих ситуаций - это задача проектирования. И функция все эти ситуации может обрабатывать, что будет отражаться на перечне выходных значений. Иными словами, сама по себе функция не скрывает информацию об ошибочных ситуациях. Это разработчик может посчитать, что ситуация не достойна внимания.
Вы правы
----
Спасибо вам за помощь!
На практике я встречал много кода на проектах, где разработчики просто игнорировали возможные ошибки. Например, они использовали операторы await или throw в вызываемых функциях и не применяли конструкции try-catch.
Если обработка ошибки не происходит непосредственно в месте ее возникновения, это не значит, что разработчик ее игнорирует. Место, где ошибку можно обработать осмысленно, как правило находится гораздо выше по колл-стеку, чем место ее возникновения, и таких мест мало - а мест, где нужно понимать конкретный тип ошибки, и того меньше. Обмазывать ради этого весь код таким густым бойлерплейтом - переусложненное решение. Если это знание вам важно, возможно, стоило взять какой-то другой язык, нежели Typescript?
Если обработка ошибки не происходит непосредственно в месте ее возникновения, это не значит, что разработчик ее игнорирует
Да, он может ее не игнорировать а просто забыть обработать
Если это знание вам важно, возможно, стоило взять какой-то другой язык, нежели Typescript?
В других языках такая же проблема, в Java например, там есть checked exception но это не удобно от слова совсем.
В golang, на уровне языка нужно всегда проверять что функция не вернула ошибку, по сути все вызовы функций всегда возвращают Result type, и постоянно нужно проверять, ошибка это или успех.
Обмазывать ради этого весь код таким густым бойлерплейтом - переусложненное решение
Если хочется делать приложения любой сложности, без повышения когнетивной нагрузки, то это стоит того.
Если хочется делать приложения любой сложности, без повышения когнетивной нагрузки, то это стоит того.
Без повышения когнитивной нагрузки?))) И это вообще цветочки в виде hello world примера, чего уж там говорить про код с реальной логикой.
class MyConsole
extends Context.Tag("MyConsole")<MyConsole, {
log: (input: unknown) => void
}>(){}
//function heyEffect(arg: number): Effect.Effect<boolean, Error, MyConsole>
function heyEffect(arg: number) {
return Effect.gen(function*() {
const logger = yield* MyConsole;
if (arg == 2) yield* Effect.fail(Error("boom"));
logger.log(logger)
return true;
});
}
pipe(
heyEffect(1),
Effect.provideService(MyConsole, {
log: (input) => console.log(input)
}),
Effect.catchAll(error => { // error соответсвует только типу Error
console.log("hey was failed with expected error", error.message);
return Effect.void // нужно вернуть новый эффект
}),
Effect.runSync //запуск эффекта
)
Я вижу тут только одну сплошную когнитивную нагрузку и минимум полезного кода.
Приложения любой сложности?)))
Писав вот такой код, к которому если вернешься через неделю то уже всё, труба, концы с концами не найдешь, что к чему от чего зачем и почему. Ну что-то как-то похоже на фантастику на самом деле.
В golang, на уровне языка нужно всегда проверять что функция не вернула ошибку
А в чем проблема писать на языке, который спроектирован так писать? Вам вообще JS и TS не нужны, это языки вообще про другое. Почему вы на Golang не пишете? Там проверка ошибок после каждой сточки. Вся ваша "пиар компания" построена вокруг - обработки ошибок, прям зацикленность на этом.
Вся ваша "пиар компания" построена вокруг - обработки ошибок, прям зацикленность на этом.
нет, моя "пиар компания" включает обработку ошибок.
Вам вообще JS и TS не нужны, это языки вообще про другое
Что это за "другое"? JS/TS это полноценные языки. Вы имеете ввиду это про фронтенд?
Почему вы на Golang не пишете? Там проверка ошибок после каждой сточки.
Опять вы про обработку ошибок, еще меня в зацикленности клеймите.
Без повышения когнитивной нагрузки?)))
Приложения любой сложности?)))Писав вот такой код, к которому если вернешься через неделю то уже всё, труба, концы с концами не найдешь, что к чему от чего зачем и почему. Ну что-то как-то похоже на фантастику на самом деле.
Просто я в одном месте показываю как создать эффект и как его запустить. И я показал как создать класс зависимости. Поэтому так много кода получилось.
Эффекты запускаются на "краях" программы. Обычно никто не пишет все функции программы в одном месте, всегда функции разбиваются по группам и каждая группа содержит свои функции.
Эффект снимает когнитивную нагрузку за счет того, что хранит в себе список возможных ошибок и необходимый контекст.
Эффект без запуска
import { Context, Effect } from "effect";
export const makeHeyEffect = (
arg: number
): Effect.Effect<boolean, Error, MyLogger> =>
Effect.gen(function* () {
if (arg == 2) yield* Effect.fail(Error("boom"));
const logger = yield* MyLogger;
logger.log(arg)
return true;
});
class MyLogger extends Context.Tag("MyLogger")<MyLogger, {
log: (message: unknown) => void
}>() { }
Что это за "другое"? JS/TS это полноценные языки. Вы имеете ввиду это про фронтенд?
Про человеческий код.
нет, моя "пиар компания" включает обработку ошибок.
1) try {} catch (e) {}
2) window.addEventListener("error", (event) => {});
3) window.addEventListener("unhandledrejection", (event) => {});
Это всё шутки, и они ошибки не ловят? Или если человек намеренно не ловит ошибку, а дает ей подняться на верх он идиот?
Опять вы про обработку ошибок, еще меня в зацикленности клеймите.

Если сделать поиск по странице по слову "ошибка". будет очень много вхождений, что говорит о зацикленности)
Эффект снимает когнитивную нагрузку
Всё равно я вижу только один шум, который кишит ключевым словом Effect. Плюс сам по себе этот код крайней своеобразный и противоестественный. Всё это и есть крайне лишняя когнитивная нагрузка.
Плюс генераторы вместо async/await, ну такое себе.
за счет того, что хранит в себе список возможных ошибок и необходимый контекст.
Я больше 13ти лет пишу и вообще никогда никаких проблем, от слова совсем из-за ошибок не было, там где программа должна продолжить свое выполнение в случае ошибки, там их люби просто обрабатывают try {} catch(e) {}, в остальных случаях они ловятся глобально и сообщение пользователю вываливается, ой, всё сломалось. JS/TS позволяет писать очень чистый код и ловить ошибки высоко, гибко и легко.
В том же Golang, делают в 99% случаев тоже самое, что по умолчанию в JS, все ошибки возвращают наверх через return err, а в JS просто этим мусором можно не засорять код вот и всё, try {} catch(e) {} на верхнем уровне все отловит.
Вы из-за зацикленности на ошибках, не понимаю конечно откуда такие проблемы, чтобы на столько все перевернуть и начать писать такой код.... Короче я даже не знаю как это всё можно прокомментировать, но выглядит это всё крайне страшно и крайне неприятно для глаз и восприятия.
Я больше 13ти лет пишу и вообще никогда никаких проблем, от слова совсем из-за ошибок не было
Ну у меня примерно такой же стаж, может больше. Вы не поняли что такое эффект и не хотите понять. Еще раз повторюсь, это не только про ошибки, а про то, как писать программы через композицию предсказуемых эффектов.
Это всё шутки, и они ошибки не ловят?
Ловят, только все типы ошибок это any
Или если человек намеренно не ловит ошибку, а дает ей подняться на верх он идиот?
Я много видел кода где разработчики пишут await await await и забивают на try catch где то в самом верху. Думаю вы лукавите, когда говорите, что разработчики специально "пробрасывают" ошибки наверх. И я не слышал чтобы разработчики позитивно отзывались про try catch, это ужасно неудобно, лучше уж Promise.catch
код крайней своеобразный и противоестественный.
Своеобразный - наверное да, потому что программа в декларативном стиле получается. Противоестественный - в чем именно?
Плюс генераторы вместо async/await, ну такое себе.
Можно не использовать генераторы, есть pipe
Короче я даже не знаю как это всё можно прокомментировать, но выглядит это всё крайне страшно и крайне неприятно для глаз и восприятия.
Вы (и не только вы), прикладываете код, который я привел для примера и который включает в себя классы ошибок, классы сервисов и еще запуск эффекта. Естественно код будет большой!
Вы тоже всю программу пишете в одном файле и в этом же файле запускаете?
Вы можете не комментировать "все это", пишите на своем Go и пользуйтесь try catch, я вам ничего не навязываю
Супер, спасибо за статью. Интересно стало попробовать. Буду ждать ещё. Прочитал все комментарии.
Кажется, что весомый аргумент просто это огромный скейл приложения. Когда нужно много исключений иметь специфичных. Ты убрал/добавил исключение и пошёл сразу везде удалил/добавил обработку соответственно. Синк на уровне типов.
Спасибо!
Синк на уровне типов.
да, типы также гарантируют что эффекты нельзя запустить если не передавать все зависимости (без эффектов на уровне компиляции я не знаю как так сделать)
Когда нужно много исключений иметь специфичных
да, еще все ошибки аккумулируются из разных эффектов. язык подсказывает какие типы и на каком этапе выполнения могут возникнуть.
Почему функция не могла принимать аргументом HttpExecutor и возвращать результат с обработкой ошибки? Решение этой проблемы при помощи библиотеки не делает ни чего что можно было сделать без этой библиотеки.
Почему функция не могла принимать аргументом HttpExecutor и возвращать результат с обработкой ошибки?
Ну вы не поняли просто смысл эффекта, он позволяет инкапсулировать в себе зависимости и возможные ошибки на уровне типа эффекта.
Решение этой проблемы при помощи библиотеки не делает ни чего что можно было сделать без этой библиотеки
В статье нет проблемного кода. Эта функция служит примером, для того чтобы понять как можно сделать тоже самое но через эффекты а не через обычный вызов функции.
Как и почему эффекты помогают писать хороший код