Автор: elsolcansado

  • Apple Vision Pro в 2026: первые впечатления

    Apple Vision Pro в 2026: первые впечатления

    Введение

    Ни для кого уже не новость, что в XXI веке существует такое приспособление, как очки виртуальной реальности. Особенно когда проходишь по какому-нибудь местному торговому центру, и в определенный момент цепляет небольшой островок, в котором предоставляют возможность надеть сие чудо-машину. А сколько эмоций испытываешь при попытке устоять на ногах, видя американские горки… Лично я шмякался о землю пару раз 🙂

    В 2024 году Apple выпустила первое оборудование подобного плана. Хотя, корректнее в данном контексте говорить именно об очках дополненной реальности, но суть не в этом. Как я уже сказал, это первый аппарат данного класса. И самой главной отличительной особенностью является бесшовное смешение цифрового и реального миров. В отличие от классических VR-шлемов, которые наглухо отсекают вас от реальности и запирают в «аквариуме», Vision Pro оснащен системой сквозного просмотра (passthrough) невероятного качества. Вы видите свою комнату, чашку кофе на столе и кота, который ходит по ногам, в высоком разрешении, а поверх этой реальности аккуратно накладываются виртуальные окна и экраны. Создается впечатление, будто бы ты сам становишься непосредственной частью цифровой вселенной.

    Что же сподвигло на приобретение?

    В самом первом посте своего блога я упомянул, что являюсь веб-разработчиком, и нахожусь в найме у одной компании. На данный момент работаю в удаленном формате. На моем 16-дюймовом Macbook Pro M3 иногда бывает маловато места: расположить браузер, редактор кода, инструменты разработчика, мессенджер + рабочий чат. В домашних условиях я для этого пользуюсь двумя 27-дюймовыми мониторами: Dell P2720D и Phillips 27M1F5500P.

    Вот они — слева направо: мои любимые DELL и Phillips. По центру — те самые Apple Vision Pro

    Работая на удалёнке не первый год, я сделал для себя вывод: сидеть дома 24/7 безвылазно — это дорога в никуда. Каждый индивидуален, и я лично знал одного парня, который мог это делать чуть ли не годами, но это точно не для меня, хоть и являюсь интровертом. Опыт просиживания целый год дома я получил — и, честно говоря, повторять его не хочется.

    Из того, что я сказал, следует вполне себе логичный вывод: необходимо менять обстановку. Обустроиться где-нибудь в парке, зайти в кафе — попить чай-кофе, посидеть в антикафе и так далее.

    Казалось бы: а причем тут очки вообще? Статья же, вроде как, про них была написана. И вообще — почему именно Apple Vision? Ведь есть аналоги, которые стоят дешевле — а функционал все тот же.

    Мой выбор пал на эти очки по одной-простой причине: наличие полной экосистемы: все мои друзья знают, что у меня есть почти все гаджеты от данного производителя. iPhone, iPad, Macbook. И те, у кого есть такой же набор — скажут вам, что ты получаешь из коробки бесшовную интеграцию с сервисами. Для меня было так вообще магией, что с Macbook можно звонить через iPhone (конечно, если вы находитесь в одной сети). И так вот: купертиновцы решили анонсировать функцию Mac Virtual Display — ее суть заключается в том, что пользователь может транслировать экран ноутбука внутри очков. При этом в реальности экран ноутбука выключается, а весь экран раскрывается как будто бы «перед глазами»

    Виртуальный дисплей Macbook внутри Apple Vision Pro

    И тут меня осенило: а ведь это можно использовать для разработки! И необязательно иметь два громоздких монитора перед глазами. И даже более того: можно работать над проектами с большой секретностью (конечно таковых у меня нет, но в жизни все может быть, разве нет?). Все, что видно в очках — видно только пользователю.

    Ощущения

    Покупал очки я с рук на Авито, поскольку финансы не позволяют взять новый прямо с магазина. Я прекрасно понимал, что это очень большой риск — искать варианты дешевле, жертвуя безопасностью, так как нарваться на мошенников не составит никакого труда. Усложняло ситуацию еще и то, что в моем городе девайса не оказалось. Но мне повезло нарваться на продавца, который переслал очки после оплаты.

    Пришли очки целыми и невредимыми: без царапин, как будто только из магазина. Правда, в три раза дешевле 🙂

    Первое, что мне пришло в голову, когда начал распаковывать очки — выглядят весьма футуристично! Непривычно, что очки приходится надевать вместе с аккумулятором, и во время его непосредственного использования нужно этот аккумулятор куда-то девать. Когда ты в одежде, выходишь куда-нибудь — эта проблема решается сама по себе. Смысл в том, что иногда я дома хожу в одном нижнем белье, и работать с очками, держа аккумулятор в одной руке — дело неблагодарное. Это так, лирическое отступление.

    Через камеры мир выглядит удивительно естественно. Но спустя время начинаешь замечать легкую зернистость. В целом это не критично, по крайней мере, для меня.

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

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

    Затем решил проверить функционал того, ради чего был приобретен девайс — это функция Virtual Display. Когда я впервые развернул виртуальный экран на всю стену, челюсть буквально отвисла. Экран можно расположить в любом месте комнаты, даже «пригвоздить» к стене. Кто бы мог подумать, что возможно организовать кинотеатр прямо у себя дома с таким же огромным экраном? У этой фичи есть три режима работы. Standard (16:9) — это привычный одиночный монитор, отличный вариант для фокусировки на одной задаче. Wide (21:9) — широкий изогнутый экран, на котором уже комфортно разместить код и превью рядом. Ну а Ultrawide (32:9) — это уже два 5K-монитора, стоящих бок о бок, с разрешением по горизонтали около 10K. На таком просторе можно разом открыть IDE, браузер, терминал и мессенджер — и ни одно окно не будет «давить» на соседнее.

    Более того, можно открывать в параллель еще окон на уровне самого visionOS. Это очень удобно для обучения: открыл окно с видео с обучалкой, на ноутбуке повторяешь. Можно этому найти большое количество применений.

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

    Пример иммерсивного режима работы в очках Apple Vision Pro

    Vision Pro — классный девайс, но дьявол, как всегда, в деталях. Например, если у вас зачешется нос под маской, приготовьтесь снимать гарнитуру целиком. Да, это беда всех очков, но все равно жутко бесит.

    Еще один важный момент: брать устройство нужно строго за ремешок (Band), как и написано в инструкции. Хватаетесь за корпус? Пожалуйста, будьте готовы к тому, что магнитные крепления могут отщелкнуться, и гарнитура полетит на пол. Я пару раз ловил этот момент в последний секунды, но нервы мне это потрепало.

    Если у вас, как и у меня, есть близорукость, имейте в виду: без специальных линз с диоптриями картинка будет мылиться. Я пока терплю, но планирую в ближайшее время закрыть этот гештальт (или все-таки сделать коррекцию зрения).

    Ну и вишенка на торте для нас, русскоязычных пользователей: родной русской клавиатуры в системе так и не завезли. Советуют качать альтернативы из App Store, но я нарвался на приложение, которое просит денег за подписку. Ставить платную подписку ради обычной клавиатуры? Нет, спасибо, сразу удалил.

    Вердикт

    Мой первый опыт с Apple Vision Pro оставил смешанные, но в целом положительные эмоции. Да, есть мелкие недочеты (вроде проблем с русской клавиатурой), которые слегка портят впечатление, но свою главную задачу устройство выполняет на отлично. Для меня это идеальная замена двум мониторам: теперь я могу уехать куда угодно и развернуть масштабное рабочее место, нужен только интернет.

    А что думаете вы? Делитесь своим опытом использования VR/AR-очков (и особенно Vision Pro) в комментариях — будет очень интересно почитать!

  • Монады без академии: как функциональные абстракции помогли мне писать меньше багов

    Зачем вообще это читать?

    В моем проекте достаточно запутанная архитектура. Когда поступает какая-либо задача на доработку — начинается полная головная боль. Иногда даже простая задача вроде «добавить поле ввода в форму» может стать головной болью, которая потом затянется на дни. В итоге проектный менеджер с недовольным лицом начинает спрашивать, когда будет готова текущая фича, а ты до сих пор не можешь разобраться, почему возникает ошибка «cannot read property «abc» of undefined», даже дебаг не помогает.

    И тут в один прекрасный момент я решил попробовать внедрить функциональные практики в проект. Его в сообществе очень нахваливают, а всеми любимый GPT утверждает, что «функциональное программирование — это про надежность, распараллеливание без проблем, декларативность, бла-бла-бла». Первая моя мысль была: «Ну, попытка — не пытка, хуже уж точно не будет».

    В итоге постепенно я начал приходить к тому, что мой код стал больше похож на какой-то поток данных, в котором можно видеть каждое звено, каждое преобразование данных, при этом без мутаций и лишних вызовов функций. Заметил, что неожиданных и необработанных ошибок обращения к undefined стало гораздо меньше. Тут меня озарило, что ФП меняет сам подход к мышлению и проектированию алгоритма: видно теперь само намерение чуть ли не на поверхности, как будто ты пишешь SQL-запрос к БД, не описывая, каким образом обращаться к базе данных, проверять граничные условия и т.п.

    Если ты увидел себя в моих мыслях или проблемах, то эта статья как раз для тебя. Начнем функциональный путь с функторов 🙂

    Функтор? Монада? Это какое-то блюдо?

    Представь, что у вас есть друг — Игорь. Он тебя пригласил на День рождения, и, как принято у людей, в такой праздник принято имениннику дарить подарок. Ты начинаешь думать про себя, что же можно подарить? Подарок должен быть полезным, применимым в реальной жизни, чтобы твой друг ссался кипятком был доволен с него. Обычно такие презенты выбирают, исходя из интересов и увлечений конкретного человека. И тут, как говорится: «сколько людей — столько и мнений».

    Много времени спустя тебе приходит мысль: «подарю-ка я микроволновку!». В самом деле, Игорь — бедный студент, снимает квартиру. А арендодатель не предоставил микроволновку (представляешь лицо Игоря?), и приходится бедному студенту покупать готовые блюда… ну или каждый раз стоять за плитой. Согласитесь — такая себе перспектива.

    Не повезло Игорю
    Не повезло Игорю

    Решено: микроволновка — хороший выбор! Ты приобретаешь ее в своем местном магазине бытовой техники и электроники, и вот она — красавица, уже на руках.

    Но как будто бы чего-то не хватает… как ты думаешь, что? Правильно — праздничной коробки! Как же подарить презент, не упаковав предварительно в праздничную коробку? А если вдруг дождь будет на улице — не дарить же испорченную технику? Или в зиму, например? Коробка — она не только же для вида, правильно? 🙂

    И в этот момент мы подходим к той самой грани между нашим реальным миром и функциональным. В коробке же может лежать все, что угодно — и ты никогда точно не будешь знать, что там лежит. Можешь поспорить, мол «а если я положил туда что-то?». Уж реальный мир наградил всех хомо сапиенсов ограниченной памятью, и кто-то с феноменальной точностью спустя долгое время ответит, что там лежит, а кто-то забудет уже спустя час. Поэтому согласно нормальному распределению будем считать, что человек (включая Игоря) забудет, что в коробке спустя относительно длительное время.

    С точки зрения программирования микроволновка представляет собой некоторые данные (число, строка, может быть, объект, массив и т.д.) — то, что хранится в памяти компьютера. И в рамках ФП1 можно оборачивать эти данные в определенный контекст — как в коробку. И программа не будет знать, какие данные именно находятся в ней, точно так же, как именинник Игорь не подозревает, что же скрывается в подарочной коробке.

    А коробка может выполнять одно: поменять содержимое (вдруг мы передумаем дарить микроволновку и захотим вручить, например, мультиварку?). В реальной жизни мы еще можем и развернуть коробку — об этом мы поговорим чуть позже.

    Благодаря аналогии микроволновки в коробке мы приходим к важному понятию функтора.

    С этим методом особенно хорошо знакомы разработчики JavaScript в работе над массивами (да, массивы — это технически функторы). Конкретно в массивах этот метод позволяет переводить один элемент в другой.

    Например:

    const array: string[] = ["1", "2", "3", "4", "5", "6"]; // имеем массив строк - нехорошо, вдруг захотим найти сумму? Складывать сейчас - получится "123456"...
    
    const parsedArray: number[] = array.map((element) => parseInt(element, 10)); // [1, 2, 3, 4, 5, 6] - теперь это массив чисел!
    
    const incorrectSum: string = array.reduce((sum, item) => sum + item); // получится "123456" - это не то, что нужно
    
    const correctSum: number = parsedArray.reduce((sum, item) => sum + item); // т.к. parsedArray содержит числа, то суммировать интерпретатор будет как числа, т.е. будет 21 

    Как ты можешь видеть, метод map взял каждый элемент из исходного массива и преобразовал по функции parseInt.

    Ты можешь создавать своих функторов, сколько угодно — главное, чтобы они реализовывали метод .map

    Алекс, ты пишешь про .map — обязательно ли называть его так?

    Это не столь важно, как ты обзовешь этот метод, главное — должна остаться суть: этот метод должен четко переводить один тип в другой. Ты можешь написать .transform, .into, .pozhalyistaPreobrazuiMenya и т.д. Правда, обычно принято использовать именно наименование .map — так поймут тебя другие разработчики.

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

    Коробка с подарком — отличная аналогия для функтора: мы можем поменять содержимое, не открывая её, просто сказав: «Пусть вместо микроволновки будет мультиварка» — это и есть .map.

    Не каждая коробка умеет безопасно открыться. Обычная функтор-коробка — она только для перевозки и трансформации. А вот если мы хотим взять подарок оттуда (или хотя бы решить, что делать, если коробка оказалась пустой), нам нужна особенная коробка — та, что знает, как себя вести в обоих случаях.

    Такие «умные» контейнеры — это уже не просто функторы, а монады (или, точнее, типы данных с дополнительными возможностями). И именно они предоставляют 2метод вроде .fold(), .chain() или .getOrElse() — чтобы вы не рисковали вытаскивать что-то из пустой коробки, а заранее предусмотрели всевозможные сценарии.

    Вот пример: ты хочешь положить в подарочную коробку не просто микроволновку, а ещё одну коробку (например, с гарантийным талоном). Обычный функтор бы просто запечатал эту вторую коробку внутри — и получилась бы «коробка в коробке».

    А монада говорит: «Стоп! Я вижу, что внутри — тоже коробка. Давай распакуем её и положим содержимое напрямую». Это называется .chain() или .flatMap().

    Благодаря этому мы можем строить цепочки операций, где каждая может вернуть «пустую коробку» — и всё равно не сломаемся. Запомните этот аспект — именно он меняет правила игры в императивном коде.

    Где это может потребоваться в реальном коде? На самом деле почти во многих местах, и стандартные Promises в JavaScript — один из них

    // 1. Запрашиваем пользователя по ID — результат обёрнут в Promise (как "коробка")
    const userPromise = fetchUser(123);
    
    // 2. Когда данные придут, извлекаем профиль — но он тоже может потребовать запроса!
    //    .then() — это как .chain(): он "распаковывает" Promise и позволяет вернуть новый Promise
    const profilePromise = userPromise.then(user => {
      // 3. Возвращаем НОВЫЙ Promise (ещё одну "коробку")
      return fetchProfile(user.id);
    });
    
    // 4. А теперь — аватар! Опять .then(), потому что fetchAvatar тоже возвращает Promise
    const avatarUrlPromise = profilePromise.then(profile => {
      return fetchAvatar(profile.userId);
    });
    
    // 5. Итог: мы получили цепочку, где каждая операция зависит от предыдущей,
    //    а "пустые" результаты (ошибки) автоматически ломают цепочку → попадут в .catch()
    avatarUrlPromise.then(url => {
      console.log("Аватар загружен:", url);
    }).catch(err => {
      console.log("Что-то пошло не так :(", err);
    });

    Хорошо, мы научились упаковывать подарки в коробки , менять их содержимое, не открывая (map), и даже аккуратно соединять цепочки операций, где каждая может вернуть свою коробку (chain). Но рано или поздно наступает момент, когда нужно решить: а что делать дальше — с этим подарком или без него?

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

    Именно для этого у «умных» коробок вроде Maybe есть метод вроде .fold() (иногда его называют .match(), .cata() или .getOrElse()). Он говорит:

    «Дорогой разработчик, я не знаю, есть ли внутри что-то — но вот два сценария: один — если подарок есть, другой — если коробка пуста. Выбери, что делать в каждом случае.»

    Ты можешь подумать: “А разве это не то же самое, что if (value) … else …?” Почти! Но .fold() заставляет тебя обязательно обработать оба случая — и компилятор (или TypeScript) не даст тебе пропустить “пустую коробку”.

    Вот как это выглядит на практике:

    // Простая реализация Maybe
    type Maybe<T> = { 
      isSome: true; 
      value: T; 
    } | { 
      isSome: false; 
    };
    
    // Фабрика: оборачивает значение в "коробку"
    const Some = <T>(value: T): Maybe<T> => ({ isSome: true, value });
    const None = <T>(): Maybe<T> => ({ isSome: false });
    
    // Метод .fold() — безопасное "разворачивание"
    function fold<T, R>(
      maybe: Maybe<T>,
      onNone: () => R,
      onSome: (value: T) => R
    ): R {
      if (!maybe.isSome) return onNone();
      return onSome(maybe.value);
    }
    
    // А теперь — пример из жизни:
    const maybeMicrowave = Math.random() > 0.3 
      ? Some("Микроволновка Panasonic NN-SN68KS") 
      : None<string>();
    
    // Используем .fold(), как если бы это был метод:
    const message = fold(
      maybeMicrowave,
      // Сценарий, если подарка нет
      () => "Упс! Подарок не дошёл — видимо, дождь испортил коробку 😢",
      // Сценарий, если подарок есть
      (gift) => `Игорь, лови свою ${gift}! Грецкие орехи уже внутри? 🥜`
    );

    Итак, резюмируем

    1. Вкратце мы познакомились с функторами и монадами (не забудьте поблагодарить Игоря и поздравить с Днем рождения!);
    2. Краем глаза увидели, как выглядят эти функторы и монады в коде;
    3. Узнали, что можно делать с функторами и монадами.

    Maybe: мой первый шаг в мире монад

    Мы уже коснулись немного Maybe, когда говорили о понятиях функторов и монад. В мире ФП он решает, какой сценарий поведения использовать в зависимости от присутствия значения. Более того, Maybe может даже возвращать значения в зависимости от сценария. И, казалось бы, это небольшая абстракция — но в коде проделывает просто уникальные вещи!

    1. Динамические колонки

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

    В TSX-разметке я обычно что-то вот такое делаю:

    import { useMemo } from 'react';
    import { baseColumns } from '@/entities/good';
    import type { Good } from '@/entities/good';
    import { Table } from '@/ui';
    import type { TableProps } from '@/ui';
    
    type GoodsTableProps = TableProps<Good> & { 
    extraColumns?: TableProps['columns'];
    }
    
    export const GoodsTable = ({ extraColumns, ...tableProps }: GoodsTableProps) => {
    const columns = useMemo(() => {
    return [...baseColumns, ...extraColumns]
    }, [baseColumns, extraColumns]);
    
    return <Table {...tableProps} columns={columns} />
    }

    Выглядит поначалу логично: если есть дополнительные колонки, значит их нужно объединять с предоставляемыми колонками, окей. А теперь представьте, что заказчик говорит вам, что порядок следования колонок нужно поменять. Очевидное решение — нужно добавить пропс columnSorter, который будет описывать, как сортировать колонки между собой

    import { useMemo } from 'react';
    import { baseColumns } from '@/entities/good';
    import type { Good } from '@/entities/good';
    import { Table } from '@/ui';
    import type { TableProps } from '@/ui';
    
    type GoodsTableProps = TableProps<Good> & { 
    extraColumns?: TableProps['columns'];
    columnSorter?: Record<keyof Good, number>;
    }
    
    export const GoodsTable = ({ extraColumns, columnSorter ...tableProps }: GoodsTableProps) => {
    
    const columns = useMemo(() => {
    if (columnSorter !== undefined) {
    return getSortedColumns([...baseColumns, ...extraColumns])
    } else return [...baseColumns, ...extraColumns]
    }, [baseColumns, extraColumns]);
    
    return <Table {...tableProps} columns={columns} />
    }

    Заметили, что здесь не так? Правильно, extraColumns может и не быть, и когда попытаемся развернуть extraColumns внутри useMemo. А это приведет к тому, что приложение повалится, и будет волшебное Uncaught TypeError: undefined is not iterable: мы не учли тот факт, что extraColumns может оказаться пустым.

    Решить это можно как-то вот так:

    import { useMemo } from 'react';
    import { baseColumns } from '@/entities/good';
    import type { Good } from '@/entities/good';
    import { Table } from '@/ui';
    import type { TableProps } from '@/ui';
    
    type GoodsTableProps = TableProps<Good> & { 
    extraColumns?: TableProps['columns'];
    columnSorter?: Record<keyof Good, number>;
    }
    
    export const GoodsTable = ({ extraColumns, columnSorter ...tableProps }: GoodsTableProps) => {
    
    const columns = useMemo(() => {
    if (extraColumns === undefined || extraColumns === null) return baseColumns;
    if (columnSorter !== undefined) {
    return getSortedColumns([...baseColumns, ...extraColumns], columnSorter)
    } else return [...baseColumns, ...extraColumns]
    }, [baseColumns, extraColumns]);
    
    return <Table {...tableProps} columns={columns} />
    }

    Выглядит теперь вроде как надо.

    Первое, что бросается в глаза — повторяющаяся конструкция [...baseColumns, ...extraColumns]. Можно это вынести в отдельную переменную, после проверки на пустоту пропса extraColumns

    import { useMemo } from 'react';
    import { baseColumns } from '@/entities/good';
    import type { Good } from '@/entities/good';
    import { Table } from '@/ui';
    import type { TableProps } from '@/ui';
    
    type GoodsTableProps = TableProps<Good> & { 
    extraColumns?: TableProps['columns'];
    columnSorter?: Record<keyof Good, number>;
    }
    
    export const GoodsTable = ({ extraColumns, columnSorter ...tableProps }: GoodsTableProps) => {
    
    const columns = useMemo(() => {
    if (extraColumns === undefined || extraColumns === null) return baseColumns;
    const realColumns = [...baseColumns, ...extraColumns];
    if (columnSorter !== undefined) {
    return getSortedColumns(realColumns, columnSorter)
    } else return realColumns
    }, [baseColumns, extraColumns]);
    
    return <Table {...tableProps} columns={columns} />
    }

    Примерно так выглядело бы на проде решение данной задачи. Проблема в том, что на этапе разработки не всегда бывает очевидно, что может быть undefined, а что — нет — и иногда даже TypeScript не помогает. И вместо того, чтобы гадать, будет ли пустым определенное значение — приходит на помощь как раз Maybe

    import { useMemo } from 'react';
    import { baseColumns } from '@/entities/good';
    import type { Good } from '@/entities/good';
    import { Table } from '@/ui';
    import type { TableProps } from '@/ui';
    import { Maybe } from '@/shared';
    
    type GoodsTableProps = TableProps<Good> & { 
    extraColumns?: TableProps['columns'];
    columnSorter?: Record<keyof Good, number>;
    }
    
    export const GoodsTable = ({ extraColumns, columnSorter ...tableProps }: GoodsTableProps) => {
    
    const columns = useMemo(() => {
    //Формируем контейнер из Maybe.of
    return Maybe.of(extraColumns)
    // обрабатываем случай, когда доп колонки есть
    .map(cols => {
      const realColumns = [...baseColumns, ...cols];
      // мы не уверены, что columnSorter есть, поэтому возвращаем отсортированные колонки, если есть
      return Maybe.of(columnSorter).map(sorter => getSortedColumns(realColumns, sorter)).getOrElse(realColumns)
    }).getOrElse(baseColumns)
    }, [baseColumns, extraColumns]);
    
    return <Table {...tableProps} columns={columns} />
    }

    Данный стиль предоставляет более естественный порядок обработки колонок, сверху вниз — как в трубе. Такое, как правило, легче читается

    2. Два источника данных, одна правда

    В философии React принято считать, что для отображения данных должен быть один источник данных, дабы избежать проблем синхронизации состояний в UI-компонентах. В самом деле, не очень хочется потом писать сотни useEffect только ради этого, потом это еще нужно отлаживать, тестировать на разных кейсах. Дай боже, чтобы это заработало. Ну или хотя бы с минимальным количеством багов 🙂

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

    В чем смысл: есть определенный <Select>, которому нужно передать значение из сущности EntityA1 или EntityA2, но только одно. При этом эти две сущности — массив (даже не спрашивайте почему — таков суровый бэк). Более того, просто передать «сырое» значение сущности — этого недостаточно, нужно преобразовать в объект, пригодный для отображения в Select

    const Page = () => {
    ...
    return (
    <div>
    ...
    <Select value={entity1 && entity1.length > 0 ? { label: entity1[0].title, value: entity1[0].id} : entity2 && entity2.length > 0 ? { label: entity2[0].title, value: entity[0].id}} : undefined />
    </div>
    )
    }

    Внушительная конструкция, согласитесь? Кроме того, за такой код на код-ревью точно надают по рукам.

    Как можно обратить внимание, здесь идет проверка на существование entity, берется 1 элемент — и делается из него объект для отображения. Ну или undefined, если значения нет.

    С помощью Maybe эта задача решается очень лаконично:

    const Page = () => {
    ...
    return (
    <div>
    ...
    <Select value={maybe(entity1).alt(maybe(entity2)).map(entity => entity[0]).map(entity => ({ label: entity.title, value: entity.id})).getOrElse(undefined)} />
    </div>
    )
    }

    В этом фрагменте кода я использовал метод .alt(), который предоставляет альтернативное значение, оставаясь в рамках контекста монады Maybe. Обрати внимание, что повторять паттерн объекта не пришлось в отличие от предыдущего примера. Кроме того, здесь можно увидеть паттерн и выделить в отдельные функции.

    import first from 'lodash/first';
    import { transformToValue } from '@/utils';
    
    const Page = () => {
    ...
    return (
    <div>
    ...
    <Select value={maybe(entity1).alt(maybe(entity2)).map(first).map(transformToValue).getOrElse(undefined)} />
    </div>
    )
    }

    Вот так лучше, правда?

    Стоит ли вам начинать?

    Подход для организации кода с помощью монад — бесспорно, мощный инструмент. Благодаря нему мысль, выраженная в коде, становится более прозрачной и ясной. Этот подход диктуется стилем декларативного программирования, когда сниппет отвечает на вопрос «что делает?», а не «как это делается».

    Тем не менее, при всех достоинствах функциональных абстракций — это не является серебряной пулей. Чтобы понять, насколько тебе подходит этот стиль программирования, попробуй ответить на несколько вопросов:

    1. Большая ли команда в проекте, и насколько она подготовлена к разработке в парадигме ФП? Разумеется, было бы странно внедрять этот подход в проектах, в которых есть уже устоявшийся code-style. Нужно будет время на подготовку, перестроение мышления. Нужно учитывать и психологические особенности каждого сотрудника: кому-то этот подход вообще не зайдет. Вы можете в этом лично убедиться, посмотрев похожие статьи на хабре или обсуждения в реддите.
    2. Критично ли бороться за доли миллисекунд времени исполнения кода? Дело в том, что TypeScript, на чем написаны примеры выше, не является исконно функциональным языком, скорее мультипарадигмальным (т.е. поощрает разные стили написания кода). Исконно функциональные языки, вроде Haskell, сильно оптимизированы под функциональную парадигму (очевидно же?). Именно поэтому любой другой язык вроде TypeScript будет проигрывать в производительности хаскелю или любому другому функциональному ЯП в рамках функционального программирования.

    Заключение

    Функциональное программирование — не серебряная пуля. Но это мощный инструмент в арсенале разработчика, который помогает:

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

    Если после прочтения вы подумали: «Хм, а ведь это действительно может улучшить понимание кода» — значит, статья сработала. Если же пока «не зашло» — тоже ок. ФП не требует немедленного обращения, оно ждёт, когда вы будете готовы.

    Спасибо, что дочитали! Если остались вопросы, нашли неточность или хотите поделиться своим опытом с монадами — пишите в комментариях, буду рад обсудить некоторые моменты.

    1. Функциональное программирование ↩︎
    2. Да, я немного упрощаю: на самом деле .fold() — это не «монадная» штука, а способ безопасно распаковать контейнер. Но поскольку мы всё равно используем его вместе с map и chain, давайте пока думать об этом как об одном наборе инструментов — а детали оставим для вечерних размышлений с чашкой чая ☕ ↩︎

  • Студийная запись

    Студийная запись

    Попробовал записать кавер на песню Alex Clare — Too close. Когда-то с этой песней пришлось выступать на отчетном концерте в своем родном городе.


    26 сентября 2025 года удалось поймать момент и попасть на студию, попробовать записать песню — никогда не поздно.

    Короче, получилось вот что. Слушаем, комментируем) Как вам?

  • Приветственное слово

    Ну вот, наконец, руки дошли до собственного блога =) Сколько раз задавался этим вопросом, когда я за него сяду, когда уже сделаю — и вот он — момент истины настал. Теперь можно делиться мыслями, обсуждать идеи, да и вообще просто не забывать о каких-то моментах жизни, которые следует запомнить.

    Сам блог пока еще сыроват, в планах сделать редизайн. Надеюсь, займусь этим не так «долго», как открывал сайт. Но это ладно, больше лирическое отступление.

    Меня зовут Алексей, на момент написания данного поста — вроде как 26 годиков) Являюсь frontend-специалистом — человек, который следит за тем, чтобы разделы сайта не уезжали за видимую область, чтобы шрифт был аккуратный и приятно читался, борец против желтого контента на белом фоне. В общем все то, что пользователь видит так или иначе на странице — заслуга специалиста по frontend-части. В этой штуке я варюсь 6 лет.

    Помимо разработческой жизни, у меня есть большое количество activity: вокал, игра на пианино, книги, фитнес, бизнес. Это помогает мне оставаться на плаву и не выгорать на основной работе. Как мне сказал один человек, никто не может играть только одну роль: по-настоящему мы раскрываемся только в разных стезях. И я понимаю, что это правда. В перспективе планирую походить на джиу-джитсу, скорее всего после того как проект интернет-магазина чуть разрастется. Кстати, буду очень благодарен, если расскажете друзьям о нем — это очень сильно поможет на старте.

    В этот блог я планирую постить по сути весь свой lifestyle: какие-то посты будут посвящены о программировании, какие-то — о музыке, что-то будет связано просто с необычными моментами в жизни.

    Не стесняйтесь делиться мыслями, идеями, пожеланиями в комментариях, можем что-то пообсуждать. Конструктивная критика, в том числе, также приветствуется.

    На такой славной ноте я заканчиваю свой словесный понос поток, периодически что-то буду выкладывать интересное.