Стендап Сьогодні
Що я зробив, що я хочу зробити, і що це все значить.
Повсякденні здобутки в форматі стендапу.
Детальніше в статті
Підписатись на RSS · 📢 Канал в Telegram @stendap_sogodni
13.06.2024
Генерація Obsidian Canvas з тексту
В Obsidian Canvas бракує можливості перетворити список задач у вузли канви. Проте канва — це просто документ JSON з простою схемою. Взявся згенерувати.
Взагалі сама генерація не ставить проблем. ID для вузла може бути будь-яким рядком, унікальним в межах канви; UUID цілком влаштовує. З розміром вузлів доведеться вгадати; хоча розумніший алгоритм можливо дивився б на довжину рядка. Можна було б зробити обробку вкладених списків, якщо розбирати Markdown, а також звʼязувати вузли між собою.
Мені поки було потрібно тільки перетворити простий список у вузли, тому й скрипт вийшов дуже простий.
А далі стало дурне питання: як його додати в Obsidian? Дурне, бо звісно ж, файл можна просто скопіювати чи навіть перетягнути у вікно Obsidian. Але мені не стільки потрібний файл, скільки ці ж вузли на канві, що вже існує. В ідеалі, скопіювати їх в буфер та вставити, куди потрібно. Спробував звичайним pbcopy - нічого не вийшло; код JSON додається текстом. При цьому в самому Obsidian можна копіювати та вставляти з канви, тож що не так?
Знайшов у XCode Additional Tools утиліту Clipboard Viewer. Вона показала, що Obsidian копіює дані у “внутрішньому форматі”, а саме org.chromium.web-custom-data
. :) Всередині з HEX-перегляду очевидно сидить той самий JSON, але як його прочитати? А ще, як записати? З вихідного коду Chromium знайшов, що дані закодовані в формат Pickle (не той самий, що в Python.) Формат простецький, за допомогою ImHex запрототипував читач.
Далі — як його записати в буфер обміну? Не те щоб хотів знову про Swift, але на macOS легше за все це зробити з оточення Apple функцією Pasteboard.setData(forType:). Але як у Swift зібрати той бінарний формат? Знайшов за рекомендацією клас ByteBuffer з офіційної бібліотеки для серверної розробки Swift NIO.
Довелося ще згадати про те, що система в мене big-endian, а формат цей little-endian… але зрештою все запрацювало та результат успішно вставився в канву! Таке рішення могло б допомогти в інших випадках, коли застосунки на Electron/Chromium копіюють у внутрішньому форматі… є ідеї?
12.06.2024
Кооперативна рівночасність
З цього відео зрозумів фундаментально важливий аспект рівночасності у Swift.
Рівночасність з Grand Central Dispatch була витискальною. Це стандартна модель рівночасності, де операційна система планує потоки та перемикає виконання як їй зручно. На ній побудовані всі сучасні ОС, та рівночасні аспекти більшості мов є абстракцією над потоками операційної системи.
Витискальна рівночасність чудово працює, поки потоків не так багато, та вони незалежні один від одного. Втім, з приходом рівночасного програмування в маси зʼявилися нові потреби. Що, якщо ми хочемо завантажити 1000 файлів, та в кожного з них буде рівночасний обробник? Робити 1000 потоків? Всі, хто так робив, швидко дізнаються, що у кількості потоків є технічні обмеження, та варто користуватися пулом, щоб на 1000 задач було лише декілька потоків (дехто каже, стільки, скільки ядер в процесора.)
Але пул потоків — то суто технічна необхідність, ускладнення програми. Тому деякі мови програмування вносять пул потоків у власну платформу та надають нам абстракції вищого рівня — це називається кооперативна рівночасність. Кооперативною, бо планувальник тепер знаходиться на рівні мови, та здатний перемикати задачі згідно з мовними конструкціями. Наприклад, з гарантією, що перемикання не відбудеться посередині синхронного коду. Або, що актор ніколи не матиме дві паралельні задачі.
От як раз у Swift async/await це механізм кооперативної рівночасності. Всі задачі з async/await
виконуються на пулі потоків, та перемикання між ними відбувається лише там, де є ключове слово await
. Ба більше, перехід async/await мало відрізняється від звичайного виклику функції — тільки тим, що стек замінюється новим. Та якщо синхронізація готова відбутися в момент виклику — то виконання просто продовжиться, без жодних пауз.
Це дуже крута та сучасна система. Сходу не знаю інших мов, де async/await
може прямо передавати контроль (пишіть, якщо знаєте.) Наприклад, в JavaScript кожний await
повертає нас у цикл подій; в Golang синхронізація через канали ніби завжди блокує одну сторону, бо це не виклик функції; в Ruby з ракторами так само.
11.06.2024
Ізоляція даних у Swift
Зміни у Swift 6 дійсно доторкнулись саме рівночасності. Якщо коротко, то гарантії ізоляції тепер будуть примусово встановлені ще компілятором.
Кожне значення в програмі отримує домен ізоляції. Це або конкретний актор, або глобальний актор, або неізольованість. Важливо, що неізольовані значення не значить вільні для використання; як тільки ми вступаємо в домен актору, то втрачаємо прямий доступ до неізольованого (часто спадкового) коду — доступ залишається тільки через await
.
Особливо цікаво, в новому SwiftUI всі компоненти автоматично належать @MainActor
. (MainActor - це абстракція для коду, який виконується на головному потоці; але не всього коду, а який добровільно входить в модель акторів.) Це значить, що в типовому застосунку SwiftUI ми не можемо більше ігнорувати питання ізоляції, що дуже добре.
До речі, офіційне рішення для простих випадків: долучати більше коду до @MainActor
; єдине що зміниться, це домен ізоляції. Проте якщо весь код у @MainActor
, то ми приходимо до рівня JavaScript: все виконання відбувається в одному потоці, обчислення гальмують інтерфейс.
Тому нам і потрібні додаткові актори, щоб прибрати повільні обчислення з головного потоку до підсистем — наприклад, завантаження з інтернету, чи перекодування GIF у MP4, як я зараз роблю. Приємно, що компілятор Swift 6 буде автоматично гарантувати ізоляцію таких модулів, та нам достатньо буде виправити помилки. Зазначу, що поки не весь SDK готовий до такої радикальної перевірки всюди. А тому я поки не можу у власних проєктах досягти чистоти ізоляції. Втім, до офіційного виходу Swift 6 ще місяці три.
10.06.2024
Модель рівночасності у Swift
🐇🐢 Рівночасність — це не тільки для серверних застосунків з тисячами паралельних запитів. В розробці застосунків з GUI рівночасність теж має критичну важливість; якщо все робити в одному потоці, довготривалі задачі будуть помітно гальмувати інтерфейс. Тому у Swift нікуди не дітися без розуміння моделі рівночасності, яка тут ускладнена історичною спадщиною та шарами переосмислювань. Сьогодні як раз починається WWDC, де обіцяють ще нововведення, тому саме час розібратися з тим, що є.
📨 Попри те, що, як і всюди, у Swift є потоки, в коді застосунків використовують не їх, а модель задач — фрагментів синхронного коду, який виконується асинхронно на чергах. Черги є послідовні або паралельні; з різними пріоритетами, замками та семафорами тощо. Ця система планування називається Grand Central Dispatch та існує ще з 2009 року, тобто ще до Swift. Як і з запитами HTTP, логіка виконання задач перекладається на операційну систему. Одна з черг є “головною” та обслуговує UI; от з неї ми й хочемо прибирати важку роботу.
🚦 Проблемою є питання власності та спільного доступу; код з різних черг повинен синхронізувати доступи до даних. Найпопулярнішим способом того є перенесення коду на іншу чергу, наприклад на головну: DispatchQueue.main.async { ... }
. Але в цілому керування доступом потребувало уважної перевірки вручну.
🎭 Щоб спростити життя, ввели модель акторів — це сучасне бачення рівночасності у Swift. Актори мають ізольований стан, тобто до нього можна звертатись тільки через async/await, та такі звернення будуть послідовними, тобто можна бути впевненим у цілісності даних.
🍱 Сенс існування актора: ми відокремлюємо частину коду (та його дані) за обмеженим інтерфейсом та кажемо: “Цей код ізольований від іншого коду та його можна виконувати паралельно з іншим.” Коли навіть в айфоні вже є 6 ядер, це важлива відзнака. Ось така незвична мені, “декларативна” модель рівночасності є у Swift.
09.06.2024
Я роблю замало планування
💹 Раніше в моїй уяві “планування” значило або стратегічне на кшталт “чого я (чи ми) хочемо досягнути цього року”, або процедурне “розбити розвʼязок на кроки”. Останнім часом зрозумів, що чого дійсно не вистачає — це тактичного планування з метою зрозуміти “що це значить та з чого складається”, а потім вже “що робити”. Причому так розглядати можна будь-які справи, навіть малі — якщо вони викликають роздуми, а не чіткий поклик до дії. Навіть навпаки, малі справи з невизначеністю здатні вбити весь ентузіазм.
🎨 Зараз я беру Obsidian Canvas. Починаю з постановки задачі. А далі скидаю на канву думки, з яких поступово вимальовується картина задачі та зрештою наступні дії. Те саме можна робити на папері, або навіть в текстовому редакторі. Корисно забарвлювати вузли різними стилями: твердження, знання, питання, висновки, дії.
➡️ Потім, коли зробив достатньо планування, виписую декілька наступних дій. Нічого дивного в тому, що вони дуже відрізняються від “очевидних” дій, які я записував раніше. Як один приклад: з мети “худнути” виплив проєкт “навчитися привабливо подавати страви”… 🍱
🏃♂️ Якщо невизначену задачу відразу почати з постановки кроків, то, по-перше, зі всією ймовірністю ми оберемо щось наївне та позбавлене змісту, що потім тільки демотивує як робити, так і планувати надалі.
🧠 А по-друге, та більш важливо, весь той контекст, що був тільки що записаний, залишився б в голові. А коли голову звільнити — то в неї полізуть нові ідеї. В тому числі навіяні тим, що ми записали.
08.06.2024
Наступні дії в GTD
Рубрика вихідного дня. З минулого разу коли я писав про контексти, дещо помінялося. А саме, я зрозумів, що проєкти які не є поточними, та дії, які не є наступними, взагалі не мають місця в системі. Взагалі система супер проста.
Отже, що є “наступною дією”? GTD відрізняється від “просто списку задач” тим, що кожна задача обовʼязково є наступною, тобто не потребує підготовки, та не є проєктом, тобто може бути закінчена за один підхід. Вся магія GTD зовсім не в тому, щоб мати “список проєктів” чи розбирати вхідні за схемкою, а в тому щоб для кожного бажаного результату визначитись зі справжньою наступною дією.
Хоч написано, що наступна дія повинна бути мінімальною, тут йдеться не про її тривалість, а про неподільність. Наприклад, в програмуванні інколи трапляється, що задача на десять хвилин розтягується на цілий день досліджень. Або відразу відомо, що задача на пару годин. Це нормально.
Але щоб “запрограмувати фічу” стало наступною дією, вона повинна не мати передумов. З цим в програмуванні теж не все легко, бо під час здається, що все ясно, поки не сядеш за роботу. Тут допоможе план реалізації, тобто попередня дія — розробити план реалізації. Або принаймні я б намагався зʼясувати, як та робота почнеться — тобто не написати код фічі Х, а зробити функцію А в модулі Б… якщо і такої ясності немає, то планування зроблено замало.
В тому і є GTD: не просто взяти з вхідних “фіча Х”, створити проєкт “фіча Х” та наступну дію “написати код фічі Х” з контекстом “@компʼютер” - тобто зробити всі рухи системи, але без жодного змісту — а свідомо провести достатньо планування — по всіх своїх проєктах — щоб коли справа дійшла до дій, то в тебе був детальний перелік наступних дій, де вже не потрібно зупинятись над кожним пунктом та думати, що воно значить. Бо парадигма GTD - це “думати кожну думку один раз”… а не щоразу, як дивишся список задач.
07.06.2024
Обережно, Query-Driven Design
Коли шукаєш потужну базу даних, можна натрапити на категорію баз, які обіцяють надзвичайну здатність до масштабування (це ж круто!) з майже незмінною швидкістю запиту (база мрій!) Прикладом будуть Cassandra, DynamoDB, ScyllaDB тощо.
Втім, при пропозиції необмеженого масштабування, варто придивитись, чи використовує база модель “Query-Driven Design / Modelling”, тобто “проєктування згідно з запитами”. За цим безневинним висловом криється небезпечна для проєкту особливість. А саме: Query-Driven Design значить, що всі запити до бази потрібно продумати наперед. Фактично це екстремальна форма оптимізації на читання: якщо коротко, то, дані в таких базах розташовуються так, щоб їх було швидко прочитати, навіть коли база сягає тисячі вузлів.
QDD-бази не є поганими! Вони дійсно надають неперевершену здатність до масштабування. Тому їх використовують компанії, яким потрібні дійсно глобальні обсяги даних. Наприклад, на DynamoDB побудована крамниця Amazon.
Проте чого точно не варто робити, це брати таку базу, поки проєкт не готовий до такого жорсткого обмеження характеру запитів. Все, що не заплановане, буде реалізоване через перебір — повільний та дорогий. А для розробки нового функціоналу буде недостатньо написати кілька SQL. Дуже абстрактно, уявіть, що на кожний новий запит потрібно написати міграцію.
…Краще, як завжди, почати з PostgreSQL, а переходити на Cassandra коли проєкт того буде потребувати.
06.06.2024
Obsidian Canvas - ідеальне візуальне доповнення для бази знань
Вже півтора року в Obsidian є вбудована “біла дошка”, тобто умовний FigJam (тільки, звісно, на одну персону) - Canvas. Це нескінченна канва, на яку можна викладати картки — текст, ілюстрації, навіть сторінки з вебу. Такий інструмент чудово доповнює і без нього потужний Obsidian, бо надає можливість робити візуальне планування, малювати діаграми, мудборди й так далі.
Хоч, звісно, канва — це вже не текстовий документ в прямому сенсі, але він зберігається у JSON, та у того JSON є відкритий стандарт. Це значить, що ти можеш легко експортувати дані або робити перетворення. Я довгий час не дивився на Canvas, бо мені здавалося, що кожна картка повинна бути окремим документом — але це абсолютно не так. Сама канва здатна містити стільки інформації, скільки буде потрібно. Включаючи картки з повною підтримкою Markdown.
Canvas дуже стрімко працює. Тому йому легко дається бути дійсно “нескінченним”; якщо місця не вистачає, можна просто пересунути частину схеми трохи далі; наближення та віддалення теж відбуваються без зусиль.
Хоч не кожна картка є документом, але включити документ в канву теж можна; так, якщо я роблю планування великого проєкту, то можу легко додавати нотатки з окремими дослідженнями. Також можна включати вебсторінки та зображення. Для будь-якого візуального планування це незрівнянно зручніше, ніж текстовий документ.
Canvas є розширюваним. На жаль, наразі це тільки незадокументовані можливості, але, вже є декілька доповнень, які додають особливі стилі для вузлів. З них хочу виділити Obsidian Canvas Style Menu, який служить точкою розширення для твого CSS. Так можна легко назбирати набір стилів для власних потреб. (Як і весь Obsidian, Canvas побудований на вебтехнологіях, та всі можливості CSS до наших послуг.) Стилі, що були накладені доповненням Canvas Style Menu, стають особливими атрибутами у JSON канви; їх легко прочитати та записати.
Як Canvas, так і доповнення до нього синхронізуються та доступні на телефоні. (До речі, в Obsidian цікава модель синхронізації налаштувань: всі вони належать до поточного схрону нотаток, та якщо придивитись, сидять там у вигляді… звичайних файлів.)
Я в повному захваті, завжди не вистачало такого інструменту, щоб і працював гарно, і був зінтегрований, і я відчував власність над своїми даними.
05.06.2024
Профілювання у Swift та оптимізація запитів
Сьогодні вперше профілював застосунок на Swift, щоб його пришвидшити. Проблема наступна: список тегів при перемиканні на режим “сортувати за частотою” помітно гальмував. Вдалося знайти причину та оптимізувати.
В XCode найбільш дружній профайлер з всіх, що я бачив; він не тільки показує профіль, а ще й виділяє підвисання UI та відразу знаходить найважчий виклик. (Хоча планка дружності у профайлерів наднизька: навіть просто графічний інтерфейс є тільки для JavaScript в браузері.)
В чому виявилася біда. У SwiftData, щоб підрахувати кількість обʼєктів у асоціації, їх потрібно повністю завантажити та ініціалізувати. А значить, для мого списку за частотою фактично завантажувалась вся база, та ще й антипатерном N+1
, та ще й, здається, не один раз, бо це відбувалося всередині алгоритму сортування.
Проблема загальна, зате рішення, до якого я звик в SQL, немає. А саме, жодних COUNT ... GROUP BY
. Або взагалі способу обходити граф обʼєктів, окрім як прямий пошук, або асоціації. Вихід, який я знайшов, простий — зберігати кількість в окремому атрибуті (так званий кеш кількості.) Тепер можна взагалі з бази діставати теги, впорядковані за частотою.
Після цього “суворе зависання” перейшло у “мікрозависання”. Профіль вказав, що хоч я виправив сортування за кількістю, то все одно завантажував всі обʼєкти, щоб показати першу та останню дату використання тегу. Для того також додав атрибут-кеш. На цьому зависання закінчились.
Висновок: виходить, у SwiftData/CoreData більше логіки перекладається на шар застосунку. Це відрізняється від звичного для вебу підходу, де база є потужнішою та відповідальнішою. Втім, і контекст застосунку на одного користувача з локальною базою суттєво відрізняється від рівночасного багатокористувацького вебу.
04.06.2024
Як я живу з <10 вкладок браузера
Хоч я розумію людей, в яких завжди в браузері безліч вкладок — та ще й через те бракує памʼяті (оперативної! або..?) - але сам все життя обмежуюсь мінімальною кількістю. Тобто від жодної до, може, двадцяти, якщо щось активно шукаю — але завжди намагаюся всі їх закрити.
Оскільки, на мою думку, безліч вкладок має обʼєктивні (витрати памʼяті) та субʼєктивні (це рудиментарна та неефективна система збереження контексту) недоліки, хочу поділитися тим, куди в мене ті вкладки зникають.
-
Обране. Багато сторінок я відвідую постійно. Легкий спосіб знайти їх швидко — додати в обране. Тоді й з рядка пошуку, й зі стартової сторінки (якщо така є) просто та швидко відкрити будь-яку закладку.
-
Список для читання. Незамінний інструмент, зберігає все, що я планую прочитати або роздивитись, але байдуже, коли. В мене для того є Reeder, але альтернатив багато. Для прикладу, цього року опрацював більш як 1000 сторінок з Reeder, які накопичились раніше. Уявіть собі, якби це були 1000 вкладок. А тепер знову 91 сторінка в черзі.
-
Нотатки. Тимчасові сторінки (наприклад, товару, який планую придбати, або пошуку, за яким стежу) скидаю в Drafts. Це чудова програма для всього тимчасового.
-
База знань. Всі посилання, що викликали інтерес, йдуть в Obsidian. Моя система організації — просто написати поруч влучні ключові слова; якщо щось шукаю довго, то як знайду, дописую всі ті слова, за якими не знайшов. А ще в мене є шорткат щоб копіювати посилання в Markdown, та він вже увійшов в памʼять мʼязів.
-
Групи вкладок. Єдиний випадок, коли мені дійсно потрібно тримати багато вкладок — це пошук або вибір альтернатив. На таке є сучасне рішення: групи вкладок; це як збережене вікно браузера, яке можна закрити, а потім відновити, коли це буде потрібно. В Safari навіть можна ділитися групою з іншими — наприклад, перекинути дружині варіанти меблів.
На мою скромну думку, безліч вкладок — це виліковне, та від неї варто звільнятися. А у тебе чому стільки вкладок?