this :: IO Diary
42 subscribers
19 photos
3 videos
10 links
Download Telegram
Channel created
Привет!👋🏾
Давай знакомиться!

Меня зовут Крис. Мое хобби и страсть — это программирование, погружение в операционные системы и всё, что связано с новыми технологиями.

Я не буду твоим учителем или наставником. Я скорее попутчик. Пока я пишу посты, я сам изучаю новые темы. Делясь этим знанием с тобой, я его структурирую и закрепляю. Если я что-то понял — делюсь, если накосячил — тоже расскажу. Так что наше общение — это mutual journey.

«Что за странное название?» — спросишь ты.

Сейчас я глубоко погрузился в изучение Haskell, и это название показалось мне идеальным. Оно отсылает к объявлению функции this, которая возвращает так называемый побочный эффект с типом Diary. Для меня это значит, что весь этот канал — и есть «побочный эффект» моего обучения. Я хочу делиться процессом и знаниями с внешним миром.

Чем я буду делиться здесь?

📊 Функциональное программирование (Haskell, теория, матчасть)
🐧 Кастомизация Linux (райсинг, удобный workflow)
⚙️ Написание скриптов (автоматизация всего)
— И всё, что хоть как-то связано с Linux и разработкой.

Очень рад, что ты здесь! Добро пожаловать в мой цифровой дневник. 😊
9🍌1
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