this :: IO Diary
42 subscribers
19 photos
3 videos
10 links
Download Telegram
Channel photo updated
Что такое функциональное программирование?

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

Функциональное программирование (далее ФП) — подход архитектурного дизайна, основанный на использовании функций в математическом их понимании. Да, это не про использование функций map и filter, стрелочных функций и рекурсии, как считают многие. Это про мышление функциями в их строгом математическом смысле. В основе этого мышления лежат несколько ключевых принципов, которые отличают ФП от императивных стилей программирования:

Чистые функции
Это фундаментальный строительный блок ФП. Чистая функция — это аналог математической функции, где выход зависит только от входных аргументов, и у нее нет никаких побочных эффектов. Она обладает следующими свойствами:

Детерминированность: Для одних и тех же входных данных всегда возвращает один и тот же результат.

Идемпотентность: Повторное выполнение процесса не меняет состояние системы (не модифицирует глобальные переменные, не пишет в файл, не делает сетевых запросов), не мутирует свои аргументы.

Иммутабельность
Данные не изменяются. Вместо того чтобы мутировать существующую структуру (например, изменить элемент массива), создается ее новая копия с желаемыми изменениями.

Функции высшего порядка

Функции, которые могут принимать другие функции в качестве аргументов и/или возвращать функции как результат. Это мощный инструмент для абстракции и композиции поведения. map, filter и reduce — классические примеры функций высшего порядка, но они являются лишь следствием парадигмы, а не ее сутью.

Декларативность
ФП — это декларативный подход. Код описывает что нужно сделать (какое преобразование применить к данным), а не как это сделать шаг за шагом (как в императивном стиле с циклами и переменными-счетчиками). Цепочка map и filter читается как спецификация результата, а не инструкция по его получению.

Есть ещё пара принципов, но они уже не влезут в пост, да и эти являются главными. Советую прочитать пост моего коллеги, там он наглядно продемонстрировал эти принципы на языке Typescript. Если есть вопросы или хочется что-то добавить — добро пожаловать в комментарии!

#functional #theory
4
🏗 Каррирование

На самом деле, функция имеет всегда один параметр. Даже, к примеру, функция add 3 10, которая принимает, как кажется, два параметра и возвращает результат сложения. Не веришь? Я тоже не верил🥴

Для дальнейшего повествования мне нужно кратко ввести тебя в определение функций в Haskell. Объявление функции из примера выглядит следующим образом:
add :: Integer -> Integer -> Integer
add a b = a + b

Последний Integer — тип возвращаемого значения. Первый и второй — типы входных параметров a и b соответственно.

А что, если я хочу сделать функцию, которая применяет данную функцию к какому-то значению? Сделаем мы это так:
apply :: (a -> b) -> a -> b
apply f x = f x

apply (\x -> x * 2) 5 -- Результат: 10

Не пугайся этих a и b в определении. Это псевдонимы типов, что-то вроде дженериков. В данном примере выражение в скобках (a -> b) — сигнатура функции, принимающей любой тип a и возвращающий любой тип b (не обязательно отличный от первого). Второй параметр — то самое значение, к которому мы будем применять функцию — имеет тип a. Последняя b — тип возвращаемого значения функции apply. Согласен, выглядит максимально запутанно, поэтому я немного тебя распутаю. Вот эквивалентная запись в Typescript, для понимания, что здесь вообще происходит:
function apply<T, V>(callback: (x: T) => V, value: T): V {
return callback(value)
}

apply<Number, Number>((x) => x * 2, 5) // Результат: 10


Этих вводных будет достаточно, чтобы разбираться с поставленным вопросом. Что такое каррирование, зачем оно нужно, и почему функции в Haskell принимают только один параметр? Перед объяснением продемонстрирую каррирование во всей красе:
add :: Integer -> Integer -> Integer
add x y = x + y

let addTwo = add 2

addTwo 3 -- Результат: 5

А так вообще можно? Можно, и очень часто нужно. Советую перечитать этот пример несколько раз, прежде чем переходить к определению, так как оно может взорвать и без того кипящую голову.

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

Применяя это определение к примеру выше, делаем вывод, что выражение add 2 вполне валидно и возвращает новую функцию, принимающую ещё один параметр — к нему мы прибавим двойку. Для тех, кто в теме — это очень похоже на замыкание (closure): внутри функции мы заключаем какое-то состояние в качестве параметра, с которым сможем работать позднее.

То есть, этот пример мы можем переписать следующим образом. Результат никак не изменится:
add :: Integer -> (Integer -> Integer)
add x y = x + y

let addTwo = add 2

addTwo 3 -- Результат: 5


В контексте Typescript это выглядит так:
function add(x) {
return (y) => x + y
}

const addTwo = add(2)
addTwo(3) // Результат: 5


Зачем это нужно? Для частичного применения функций. С помощью этого метода можно очень удобно делать полезные функции и упрощать лямбда-выражения. Пример:
let list = [1, 2, 3, 4]

-- Эти три записи эквивалентны
let incrementList a = map (\x -> x + 1) a
let incrementList' a = map (1 +) a
let incrementList'' = map (1 +)

--
let getPositives = filter (> 0)

getPositives [-2, 5, 3, 0 -9] -- Результат: [5, 3]


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

#functional #theory #haskell
🔥3
😴 Lazy Evaluation

Любой программист хочет сделать максимально эффективную с точки зрения памяти и производительности программу. Андреас Румпф, разработчик языка Nim, специально для этого разработал несколько механизмов сборки мусора, хотя в то время пользователи очень нуждались в фичах. А можно ли как-то обойтись без шаманских ритуалов со сборщиками мусора, механизмами компиляции и прочего, посмотреть на оптимизацию с другой стороны? Можно – придумать такой механизм, который пропускает инструкции, не требующие сиюминутного выполнения.

Мой репетитор по физике был очень ленивым человеком: "Я подставлю значения в эту формулу в самый последний момент, когда переменных будет совсем немного". В действительности это экономит очень много времени и сил – сложность вычислений сильно снижается, так как часть переменных просто сокращается. Если с самого начала оперировать цифрами, выполняется слишком много лишней работы. Именно это легло в основу функциональной парадигмы.

Ленивые вычисления – это фундаментальная стратегия выполнения кода, при которой любое выражение вычисляется только в тот момент, когда оно действительно нужно. Сразу перейдем к примерам:

arr = list(range(0, 2**32))
print(arr[0:5])

Сколько памяти эта программа потребует для выполнения? Сколько времени понадобится на аллокацию памяти, чтобы в итоге вывести 5 первых элементов? Много.
В императивных языках преобладают строгие (энергичные) вычисления, являющиеся противоположностью ленивых – выполняем все именно так, как и написано, даже если результат не потребуется. Поэтому сначала создастся массив на 2³² элементов, а потом выведутся первые 5 элементов.

Тот же пример на Haskell:
take 5 [0..2^32]

А сколько здесь памяти потребуется? Столько, сколько нужно для 5 чисел типа Integer. Определение массива [0..2^32] – это, на самом деле, не определение, а обещание: "Да, я сделаю тебе массив из 2³² элементов, но только тогда, когда тебе это будет нужно". А функция take – это функция, которая требует результат этого обещания. Он обращается к несуществующему массиву и получает 5 элементов один за одним. Память и процессорное время тратятся только на то, что пошло в финальный результат.

Зачем это нужно?

1. Работа с бесконечными структурами данных. Это визитная карточка ленивости. Ты можешь описать бесконечную последовательность (все простые числа, поток данных от датчика, числа Фибоначчи) и использовать её конечную часть, не опасаясь зависания:
   -- Бесконечный список всех натуральных чисел
numbers = [1, 2, 3, ..]
-- Взять от него ровно 10 элементов — и остальные не будут созданы
take 10 numbers

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

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

Но не может ведь быть все настолько радужно. Где-то должен быть подвох.

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

Итог: Lazy evaluation позволяет писать более декларативный, выразительный и зачастую более эффективный код при умелом использовании. Это краеугольный камень многих функциональных языков и полезный инструмент, который проник даже в строгие императивные языки (yield и генераторы), помогая разработчикам оперировать данными более гибко.

#functional #theory #haskell
3
Долгожданная суббота. Лежать как тюлень, кушать и смотреть One Piece?
А может лучше...
Поставить Gentoo? (пока на виртуалку)
😁2
Спасибо папаша😃
🤪2🍌1
🍃Чистота функций

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

Как я уже писал в посте про основополагающие принципы функционального программирования, при вызове чистой функции её результат зависит только от входных параметров. С одними и теми же параметрами чистая функция возвращает одно и то же значение, никак не влияя на внутреннее состояние системы. Это два свойства чистых функций – детерминированность и идемпотентность соответственно. Функции, нарушающие одно из свойств (или оба), имеют некоторые побочные эффекты, размазывающие результат функции по системе: меняют локальное состояние (например, записывают данные в локальную БД), обращаются к внешним ресурсам, обращение к которым может завершиться ошибкой, или возвращающим переменные данные (HTTP-запросы, обращения к памяти, вывод в консоль, получение timestamp и так далее). В терминологии Haskell для таких операций есть отдельное название – действие ввода-вывода. Но сегодня не будем про Haskell, как-нибудь в следующий раз.

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

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

Идеальная архитектура приложения в этом смысле выглядит так:

1. Ядро: Полностью чистое. Это бизнес-логика, алгоритмы, преобразования данных. Оно ничего не знает о базах данных, HTTP-запросах или файловой системе. Оно просто принимает данные и возвращает результат. Это самая ценная и стабильная часть приложения.

2. Оболочка: Тонкий слой, отвечающий за взаимодействие с внешним миром. Он получает сырые данные "снаружи" (из БД, от API-вызова), прогоняет их через чистое ядро и затем (или перед этим) выполняет необходимые эффекты: запись в БД, отправку ответа, логирование.

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

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

#functional #theory
🔥4👍1
О чем рассказать в первую очередь? Не знаю, какую написать вперёд - все темы нравятся одинаково
О чем рассказать в следующем посте?
Anonymous Poll
69%
Как я учусь
44%
RAG - как это ведать
38%
Почему кругозор очень важен
К слову, у меня есть лайв-канал – там щитпост, мемы, собаки, ремонт на коленке и другие аспекты моей жизни. Сейчас начался танцевальный сезон, поэтому всяких танцулек там будет много

https://t.me/chrisonych
this :: IO Diary pinned «К слову, у меня есть лайв-канал – там щитпост, мемы, собаки, ремонт на коленке и другие аспекты моей жизни. Сейчас начался танцевальный сезон, поэтому всяких танцулек там будет много https://t.me/chrisonych»
В дополнение к текстовым постам я также буду вести заметки по разработке, так как у меня появилась хорошая идея для OSS-проекта, к реализации которой я приступлю с понедельника.

Я играю на бас-гитаре, и сосед-гитарист пригласил меня играть вместе с ним — вот мы уже ко второму совместному квартирнику готовимся. Если раньше подобранные на слух несложные партии я записывал на бумагу, то сейчас я не знаю, как держать это всё на бумаге так, чтобы можно было разобраться без 100 грамм, просто бегло взглянув на схему. К тому же партии сложные, и держать все в голове достаточно тяжело.
Я нашел в пару сервисов, позволяющих удобно создавать табы. Но ограничение в 15 песен меня смущает😃

Коротко об идее: хочу дойти до self-hosted решения для организации и хранения репертуара и написания табулатуры. В веб-интерфейсе можно будет прописывать табулатуру для гитары и бас-гитары, сохранять её и структурировать в плейлисты. Прямо в редакторе можно будет прослушивать табулатуру в синтезе
🔥2👍1
Функциональщина никуда не денется, заметки по разработке будут отдельной рубрикой
Может бэк на Haskell написать?..
😁3
✍️ Как я учусь

Я не могу провести ни одного дня, в который не узнал бы ничего нового. Большая часть моего свободного времени уходит на обучение, будто то программирование, резьба по дереву, игра на музыкальных инструментах, танцы, наука, рисование, изучение новых языков или что угодно другое. Я полюбил учиться ещё в школе, но не как большинство – пока все зубрили материал, я изучал его по-своему, так ещё и, в соответствии с принципом ленивых вычислений, в самый последний момент – только тогда, когда это действительно понадобится. Ещё сильнее я полюбил этот подход благодаря моему репетитору по физике – на своем примере он показал мне, что эта схема работает очень хорошо.

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

Я не верю в «сначала вся теория, потом хоть потоп» – это смертный приговор для мотивации. Поэтому я делаю наоборот. Хочу вырезать через ворона из дерева? Беру резец и пробую, царапая заготовку и совершая кучу ошибок, но, в конечном счёте, добиваясь какого-то результата. Решил разобраться с новым фреймворком? Не читаю документацию, а сразу перепечатываю чужой код из туториала, чтобы через пять минут увидеть на экране работающее, хоть и чужое, приложение. Это даёт мощнейший импульс для дальнейшего развития по двум причинам:
У меня в руках появляется результат работы. Да, корявый, но результат, которого я добился сам, пусть и с туториалом, если говорить в контексте разработки. Это даёт мне большой прилив положительных эмоций и вызывает чувство маленькой, но победы.
Я могу проанализировать свои ошибки и предположить, что можно было сделать лучше. И вот тут-то и наступает самый важный момент: у меня появляются правильные вопросы. Я больше не листаю документацию как сонный справочник, а целенаправленно ищу в ней ответы на конкретные «как?» и «почему?». Изучение теории становится более продуктивным, поскольку есть практический контекст. Она не просто рассказывает, а объясняет то, с чем я уже столкнулся руками.

После первой попытки я с ощущением «это вы падажжите, я еще доку не смотрел» иду изучать теорию. И только с этим живым интересом и положительными эмоциями я открываю теорию, иначе процесс получения новых знаний будет для меня самой страшной пыткой. После получения необходимого корпуса знаний я иду закреплять полученные знания на практике. Это может быть абсолютно неадекватная и невыполнимая идея вроде «Ну, Hello World я написал, теперь можно бахнуть драйвер для видеокарты». И абсолютно серьезно сажусь изобретать, догугливая необходимые знания на ходу. Да, вероятнее всего я не доведу идею до конца и не получу результат, но на данном этапе мне важен уже не результат, а то, что я получаю в процессе – новые знания. Именно поэтому у меня больше 30 репозиториев на гитхабе (после чистки, до этого их было 64). В ходе этих экспериментов я нахожу свои слабые места, интересные ресурсы и все, что каким-то образом может помочь мне в развитии, и прямо на месте прорабатываю все найденное.

Это мой цикл: Попробовать -> Восхититься результатом -> Разобраться -> Экспериментировать. Такой подход может казаться хаотичным, но он невероятно эффективен, потому что построен не на долге и принуждении, а на живом любопытстве и азарте.

#philosophy
❤‍🔥3🔥1
Написал, что не могу провести ни одного дня без изучения чего-то нового, а сам два дня по-черному тюленю😃
Такое редко бывает, хоть пару слов корейских за день да выучу хотя бы. Отдыхать тоже надо, так как, всё-таки, именно во время отдыха мозг строит новые нейронные связи и систематизирует новые знания. Не забывайте отдыхать🙏🏾
👍3
На лайв-канале делился темой Обсидиана и новым графом, в которым были только посты и ежедневные заметки. В комментах к посту я поделился, что прошлое мое хранилище мне больше не нужно, поскольку эти заметки я делал для работы в научной группе, и, казалось бы, биология, нейрофизиология и высшая математика мне больше не понадобятся.

Сегодня мне приснилось, будто у меня сгорел целый дом, в котором я хранил книги и разные рукописи. Полдня думал, к чему мне это приснилось, и вспомнил, как чуть не отправил в мусорку ~70 проработанных заметок, написанных своими мыслями, не скопипасченных или перепечатанных. Добавил их к своему новому хранилищу, разложил все в новой структуре по полочкам.

Цените знания, даже если они вряд ли вам понадобятся🙏🏾

#philosophy
2❤‍🔥1
Статья про RAG в процессе написания. Она получается очень объемной, поскольку я постарался раскрыть тему настолько подробно, насколько это возможно. Будет много схем и картинок, в рамках телеграмного поста сделать это не представляется возможным. Поэтому, для больших сложных постов я буду публиковать ссылки на telegraph для чтения прямо внутри телеграма.

Возможно я переборщил местами, но в целом должно быть интересно
🔥4
Люблю LaTeX🫣
🥰3😨2🤯1
Сегодня я пробно постримил на на твиче. Причин этого не делать я не нашел, поэтому все заметки по разработке редактора партитур перенесутся в формат стримов с записью. Лайв-кодинг, всё такое😃
Я буду стримить там не только что-то связанное с разработкой — сегодня я стримил, как добиваю 80 уровень шамана в World of Warcraft. Связанные с кодингом стримы я буду отдельно анонсить здесь

Сразу можете оформить подписочку:
https://www.twitch.tv/lsdrfrx
3
А лайв-кодинг будет очень и очень скоро — я участвую к хакатоне ЛЦТ. Задача предстоит интересная, но об этом я расскажу завтра🤫
❤‍🔥2
Сегодня на стриме разбираем, что такое Backend Driven UI. Лайв-кодим💪🏾

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

Предварительное время начала: 13:00

Кто будет – тыкните реакцию 👀

#frontend #livecoding
👀41