Cіпласпластик
470 subscribers
147 photos
32 videos
2 files
242 links
🇺🇦 Про айті та дотичні теми загалом, ну й трохи про C++.

Мої емоджі:
https://t.me/addemoji/AdaptiveDevIcons
https://t.me/addemoji/VehicleBrands
Download Telegram
Робив задачі одинадцятого дня на Elixir 💻. Хотів колись опанувати цю мову, щоб писати ботів для телеграму не на Python, тож пару років тому сів за Erlang 💻 (не шукаю простих шляхів — вирішив йти від «початку»). Але врешті закинув.

Зараз же відкинув принциповість, просто сів та почав писати. І мені дуже сподобалося. Це буквально Erlang з нормальним синтаксисом. Дуже подобається пайп |> (хоча звичайна крапка все одно була б краще, бо одне натискання на клавіатурі замість трьох), подобається зіставлення з шаблоном тощо. Гляньте ось:
def solve_like_noob(iterations) do
data = read_data(@file_path)

1..iterations
|> Enum.reduce(data, fn _, acc ->
acc |> Enum.flat_map(&replace_naïve/1)
end)
|> length()
end

Не обійшлося й без WTF-моментів. Наприклад, функції не можуть бути використані в гардах інших функцій чи в case — можна тільки макроси. Мабуть, якась внутрішня специфіка. Але не сильно заважало.

Звісно, у моєму випадку писати на Elixir сенсу було не надто багато, бо він не в таких задачах себе найкраще проявляє. Я там намагався трохи погратися з багатьма процесами, але врешті дропнув цей шматок коду. Втім чисто відчути мову мені вистачило. Я подумки це застосував до того, що вже знаю про Erlang, і загальна картина мені подобається — точно не останній мій раз використання Elixir.
Please open Telegram to view this post
VIEW IN TELEGRAM
Ну шо сказати. Втомився я трохи. Мало того, що відстав майже на тиждень (зараз лише 2,5 дні наздогнати) і що пишу щоразу новими для себе мовами, так і дописи, як оцей, займають багацько часу. Але хто, якщо не я?! )

Отже 12-й день розвʼязував на Dart 💻. Хотів глянути на мову, бо існує Flutter 💻: як би я не любив QML, є ненульовий шанс, що рано чи пізно він накриється, тож треба дивитися альтернативи.

Підсумок такий: Dart — це JavaScript, яким він мав бути. І ні, не тому, що JS поганий, а цей класний, а тому, що тоді б принаймні назва була виправдана. Бо від Dart дуже стійке відчуття, наче пишеш на Java з дрібними домішками інших мов того ж типу.

І це якось мене бентежить. Типу, сідаєш такий пописати на релаксі, а воно тобі хуяк по обличчю оцим своїм синтаксисом. Ну, типу, якби я хотів постраждати, то я б, мабуть, одразу взяв нормальну мову, яка компілюється не в JS, м? Хз, короч.

Ну, принаймні дженеріки є та навіть Set у стандартній лібі, а не як в Go — гугл перевершив себе цього разу. Ось рандомні шматки коду, щоб ви трохи відчули синтаксис:
int discount_cost() {
return area * edges().length;
}

Set<Edge> edges() {
Edge? findEdge(Point point, Side side) {
const check = {
Side.Above: Point(0, -1),
Side.Below: Point(0, 1),
Side.Left: Point(-1, 0),
Side.Right: Point(1, 0),
};
// ...

@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
if (other is! Point) return false;
return row == other.row && col == other.col;
}

@override
String toString() => '$start -> $end ($direction)';

Matrix<bool> visited =
Matrix(List.generate(rows, (_) => List.generate(cols, (_) => false)));

Ну ви зрозуміли.

З приємного: доволі якісний тулінг, зокрема плагін для vscode. Працює швидко й ніби безпроблемно.

Сама по собі мова не складна й доволі непогано структурована. Як нині модно, є константи компіляції (const), рантайм-константи (final) та змінні (var). Є інтерполяція рядків, nullable-типи, скорочений синтаксис для методів (через =>), ну й усяке таке. Є якийсь свій прикол для тредів, який вони назвали ізолятами (isolates), але мені не довелося використати.

З цікавого: підтримка hot reload, утім хз, чи це фіча мови, чи чисто Flutter. Так чи інакше таке нескладно зробити, коли транслюєшся в JavaScript.

Та попри все це мова якась неприємна мені. Якась суха й без життя. В ентерпрайзах на такій писати якраз норм, мабуть. Як-он Toyota з їхньою infotaintment-системою. Але я за можливості уникатиму.

Чи то мене просто гугл аж настільки дратує?
Please open Telegram to view this post
VIEW IN TELEGRAM
Тринадцятого дня була доволі проста, але цікава задача. Треба керувати роборукою в ігровому автоматі, з яких іграшки дістають, знаєте такі? Claw machine англійською. Вам дають отакий інпут (їх у файлі багато):
Button A: X+94, Y+34
Button B: X+22, Y+67
Prize: X=8400, Y=5400

Натискання на A коштує 3 токени, а на B — 1 токен. Треба витягнути приз зі вказаних координат так, щоб було найдешевше. Я глянув на це, і щось стало та-а-а-ак впадлу казати комʼютеру крок за кроком, як розвʼязати цю систему рівнять, що я вирішив натомість просто пояснити йому, що треба, а він нехай сам дуплиться. І ні, я не про LLM.

Я вирішив писати на Prolog 🦉! Зараз і вас навчу.

Раніше я вже робив три чи чотири підходи до цієї мови, і щоразу невдало. Прям відчувався брак теоретичної бази. В універі нас цьому не вчили на жаль — там з цікавого був тільки Haskell (десь між лабами по VHDL та створенням формочок в MFC).

Всі мануали — це також якесь дно. Задовбало читати типові приклади про те, хто там кому батько та чий собака — друг Сократа. Бо підсвідомо розумієш, що між цими прикладами та практичним застосуванням ціла прірва невідомого. Треба було щось міняти.

Тож для початку я все ж звернувся до ШІ-шки та попросив написати, як виглядатиме читання з файлу. І раптом мене осяяло, як це працює!
Please open Telegram to view this post
VIEW IN TELEGRAM
У Prolog 🦉 фішка, що все описується предикатами. А ми з вами звикли, що предикат — це функція, яка повертає bool. І я все ніяк не міг зрозуміти, а як же звідти щось інше діставати. Зрозуміло, що там зіставлення з шаблоном, як в якомусь Haskell чи Erlang, але все одно. Навіть дивився приклади — та не вкладалося в голові. Розповідаю, отже.

Є, наприклад, предикат number_string. Працює отак:
?- number_string(42, "42").
true.

Можна перевіряти, чи число співпадає зі своїм представленням у вигляді рядка. Зручно, але шо далі? А ось що:
?- number_string(42, Str).
Str = "42".

?- number_string(Num, "42").
Num = 42.

Тобто ви просто ставите замість одного з параметрів змінну (з великої літери), а воно, якщо може, матчить його зі значенням, з яким предикат видаватиме true. Причому це можна робити в обидва боки! Іншими словами, у вас параметри предиката — це і «вхідні», і «вихідні» разом. How cool is that?!

Ось парсинг першого рядка вхідних даних, що я давав на початку попереднього допису:
parse_button_a(Line, X, Y) :-
split_string(Line, ",:", " ", ["Button A", Xs, Ys]),
parse_num('X', Xs, X),
parse_num('Y', Ys, Y).

У parse_button_a я за задумом першим параметром даю текстовий рядок й очікую, що X та Y воно виведе само. Для цього мають виконуватися правила під ним. Перше правило: якщо я цей рядок сплітаю по комі та двокрапці (і прибираю зайві пробіли), то очікую, що останній параметр заматчиться зі списком з трьох штук. Перша — це сталий рядок "Button A", а другий та третій — це рядки зі значеннями для координат (наприклад, "X+94" та "Y+34"). Два наступних правила вже працюють якраз з ними й фактично дістають з рядків числа. Глянемо всередину.
parse_num(Var, String, Number) :-
atom_chars(String, [Var, Sign|Rest]),
atom_chars(NumStr, [Sign|Rest]),
atom_number(NumStr, Number).

Тут три параметри — назва координати (Var), рядок з попереднього правила та відповідне числове значення. atom_chars матчить рядок зліва зі списком атомів (а ля констант) справа. Тож перший з них бере рядок і деконструює його на Var ('X'), Sign ('+') і решту (['9', '4']). Друге правило фактично робить це у зворотній бік: справа ми даємо ['+', '9', '4'], а зліва отримуємо '+94'. Цими двома правилами ми, по-перше, переконалися, що це справді X, який ми очікуємо, а по-друге, ми його відрізали з рядка, щоб не заважав. А третій предикат матчить атом (вважайте, що рядок) зліва ('+94') з числом справа (там буде 94). І потім, якщо все це заматчилося, це число йде в параметри самого parse_num.

Ну короч ось так я запарсив увесь файл, а потім читернув та використав солвер лінійних рівнянь ))
solve_machine(Ax, Ay, Bx, By, Px, Py, A, B) :-
A in 0..100,
B in 0..100,

A * Ax + B * Bx #= Px,
A * Ay + B * By #= Py,

Tokens #= A * 3 + B,

labeling([min(Tokens)], [A,B]).

Він «отримує» перші шість параметрів у вигляді чисел і підбирає й «виводить» такі A й B, щоб ціна була найменша. Вуаля! )

Звісно, Prolog цим не обмежується. Теоретична база все одно потрібна, бо він і так доволі повільний (пів секунди на дві задачі), а на додачу дуже легко запитати щось, відповідь на що він шукатиме мільярд років. Попри це я зацінив!

І навіть при розвʼязанні задач на 17-й день іншою мовою, я у момент відчаю швиденько написав на Prolog робочу віртуальну машину на 8 команд з трьома регістрами памʼяті, щоб звірити результати.

Дуже раджу теж спробувати.
Please open Telegram to view this post
VIEW IN TELEGRAM
Про мову Ada я чув ду-у-у-уже давно і весь цей час мав враження, що вона давно померла й забута. Аж доки кілька років тому мій друг @repa4ok не розповів мені протилежне (дякую!). Мова розвивається (останній стандарт — 2022 року), спільнота розвивається, навіть менеджер пакетів є.

Мову колись створили для військових і назвали на честь першої програмістки у світі (так, першою була жінка, якщо хто не знав). Вирішив нарешті дати цій мові шанс і спробувати її для розвʼязання задач 14-го дня.

Синтаксис дуже схожий на Pascal (принаймні той, який памʼятаю я). Писати нею доволі важко, коли бачиш її вперше, бо є велике різноманіття конструкцій, а ментальної моделі для них в голові бракує. Попри це читається такий код вкрай легко — тут вам не C++ і, боже збав, не Rust.

Компілятор скаржиться на неправильне форматування, тож краще одразу налаштувати собі автоформат на збереження файлу. З цікавого: за стайл-гайдом відступи — 3 (три!) пробіли 🙃 Ще компілятор доволі легко знаходить похибки в тексті й каже щось адекватніше за «unexpected token»:
procedure Solve_First is
package IO renames Ada.Text_IO;
begin
delclare
Counter : Integer := 0;
-- and so on...

Помилка:
Compile
[Ada] day_14.adb
day_14.adb:8:07: error: incorrect spelling of keyword "declare"


Але найголовніше, що мене до душі — це опис власних типів. Наприклад, в Ada не прийнято всюди використовувати «голий» Integer, і це правильно, бо він може значити що завгодно. Натомість ви «моделюєте» домен вашої задачі у вигляді типів, а компілятор вам допомагає їх дотримуватися (і лупить по руках, коли ви цього не робите). Ось шматочок мого коду:
Width : constant := 101;
Height : constant := 103;

type X_Coordinate is mod Width;
type Y_Coordinate is mod Height;

type Matrix is array (X_Coordinate, Y_Coordinate) of Integer
with Default_Component_Value => 0;

type X_Movement is range -Width .. Width;
type Y_Movement is range -Height .. Height;

type Point is record
X : X_Coordinate;
Y : Y_Coordinate;
end record;

type Velocity is record
DX : X_Movement;
DY : Y_Movement;
end record;

type Robot is record
Pos : Point;
Vel : Velocity;
end record with Put_Image => Robot_Put_Image;

За умовами задачі треба було змоделювати переміщення роботів. Одна з цікавинок саме задач цього дня: коли робот доходить то стіни, то автоматично виходить з іншого боку, як часто роблять в іграх. І так зійшлося, що в Ada це можна описати!
type X_Coordinate is mod Width;

Ось це буквально значить, що координата X змінюється від 0 до 100 (Width - 1), а якщо раптом стає більше або менше, то врапається на нове коло. З іншого боку жодних автоматичних приведень типів немає, як і в Haskell, тож всі конвертації тільки ручками. До речі зараз вже бачу, що деякі типи можна було описати краще.

Ще в мові є дженерики (трохи використав), є ООП (не бачив), є якісь засоби для формальної верифікації програм тощо. Дуже багато всього, але мені на жаль поки нема чого ще додати, бо я пів дня з нею всього посидів.

Точно повернуся до 💻, тим паче що, як виявилося, існує тулчейн під Xtensa-проци, які стоять в ESP32, а в мене якраз пет-проєкти на них.
Please open Telegram to view this post
VIEW IN TELEGRAM
Сьогодні не про Advent of Code, хоча в мене ще лишилася пара повʼязаних тем на обговорення (днями напишу).
Please open Telegram to view this post
VIEW IN TELEGRAM
За порадою пана @dmytrish вирішив покодити задачі 17-го дня на Crystal. Статично типізована мова з синтаксисом Ruby — звучить чудово.

Перша задача полягала в написанні власної віртуальної машини з трьома регістрами, про що я вже згадував у кінці допису про Prolog. Це було легко й відносно приємно. Хоча вже тут стало ясно, що синтаксис настільки ж лаконічним зробити не вийде. Наприклад, там є 8 команд, що виконують певні операції над регістрами. При цьому сама вхідна «програма» записана тупо числами, тому природне бажання — зробити мапку з чисел на відповідні функції. Очікуєш щось отаке:
@@ops = { 0 => adv!, 1 => bxl!, 2 => bst!, 3 => jnz!, 4 => bxc!, 5 => out!, 6 => bdv!, 7 => cdv! }

Насправді ж найкоротше, що я зміг написати, щоб це запрацювало:
@@ops : Hash(Int8, Proc(Day17Solver, Int64, Int16)) = {
0_i8 => ->(s : Day17Solver, v : Int64) { s.adv!(v) },
1_i8 => ->(s : Day17Solver, v : Int64) { s.bxl!(v) },
2_i8 => ->(s : Day17Solver, v : Int64) { s.bst!(v) },
3_i8 => ->(s : Day17Solver, v : Int64) { s.jnz!(v) },
4_i8 => ->(s : Day17Solver, v : Int64) { s.bxc!(v) },
5_i8 => ->(s : Day17Solver, v : Int64) { s.out!(v) },
6_i8 => ->(s : Day17Solver, v : Int64) { s.bdv!(v) },
7_i8 => ->(s : Day17Solver, v : Int64) { s.cdv!(v) },
}

Довелося додати зовсім трошки візуального сміття, щоб компілятор збагнув, що я від нього хочу. Мабуть, звичайний switch-case (який тут називається case-when) був би навіть краще.

Різноманіття числових типів — це окрема розмова. Є від Int8 до Int128 і від UInt8 до UInt128, клас! Але коли ти пишеш звичайний числовий літерал на кшталт 42, то це завжди Int32. Тобто хочу я в тесті передати у свій розвʼязувач задачі «програму» як список чисел, але я не можу написати це як:
[0,3,5,4,3,0]

Компілятор бачить, що функція приймає Array(Int8), і він, мабуть, бачить, що всі ці числа легко влізають в Int8, але ні: «Не можу, — каже, — конвертнути Int32 в Int8». Доводиться писати отак:
[0_i8,3_i8,5_i8,4_i8,3_i8,0_i8]


Ідемо далі. Рівночасність підтримується, але реалізується через fibers (а ля корутини), які спілкуються через channels. Наче норм, але тредів немає — все працює в одному (і ще GC у власному), тож розпаралелити якісь обчислення таким чином не вийде, як я зрозумів.

Ну а в решті моментів Crystal — це Ruby з типами. Я сам на Ruby останній раз писав, коли була ще версія 1.9, і, якщо чесно, я б зараз був переконаний, що мова давно вмерла, якби нею не писав автор каналу Стендап Сьогодні пан Шевцов та зрідка не згадував у себе в каналі пан Рожков.

У Ruby мені завжди подобалася легкість створення власних DSL. І наскільки я можу судити, Crystal це також вміє. Наприклад, тут вже є так званий Spec, який дозволяє писати BDD-тести. Мені дуже стало в пригоді. Пишеш щось на кшталт:
describe "#parse_registers" do
it "returns a hash of registers" do
registers = [
"Register A: 729",
"Register B: 2",
"Register C: 3",
]
Day17Solver.new.parse_registers(registers).should eq({a: 729, b: 2, c: 3}.to_h)
end
end
describe "runs" do
it "the first example and outputs 4,6,3,5,6,3,5,2,1,0" do
solver = Day17Solver.new "data/task.example"
solver.run
solver.@output.should eq([4, 6, 3, 5, 6, 3, 5, 2, 1, 0])
end
end

Запускаєш crystal spec, і воно все перевіряє й видає результат. Дуже зручно, бо не треба якісь окремі ліби підʼєднувати чи щось налаштовувати — воно одразу є при створенні проєкту.

До речі щодо створення. Вельми непоганий тулінг у Crystal! Взагалі не було з цим проблем.

Короч, підсумок: мова наче й непогана, компілюється, перевіряє типи, працює явно швидше за Ruby, проте, з іншого боку не зовсім ясно на кого вона орієнтована. Хто і для чого її взагалі використовує? Є тут хтось, хто пише нею?

P.S. Усіх з Різдвом, до речі 🎄
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
Щоразу, як дивлюся або читаю пана Соловйова, хочеться спробувати Clojure, особливо на тлі незрозумілостей з Red.

Ліспи з їхньою купою варіацій і REBOL з наразі єдиним нащадком Red — це D&D 🎮 світу програмування. Є десяток базових правил, а далі все обмежується лише вашою фантазією (і спроможностями заліза гг). Обидва мають суперпростий синтаксис — можна навіть сказати, що ніякого синтаксису немає: ви просто пишете слова, а потім наділяєте їх власними значеннями. Звучить як казка.

Я вже намагався зробити підхід до Clojure десять років тому, але вочевидь був неготовий ментально. Зараз же зробив другу спробу і… не зміг себе пересилити: дивлюся в цей короткий лаконічний код, а подумки бачу, як JVM нестямно жере памʼять мого компа, і уявляю, як мені доведеться йти крізь ієрархію тек, щоб нарешті знайти там свій єдиний файл з кодом.

На щастя з одного зі стрімів пана Соловйова я дізнався про існування мови Janet 👩‍🦰 — дуже схожого на кложу діалекту ліспа, який написаний на сішці, важить майже нічого, легко інтегрується з сішним кодом в обидва боки тощо. І спробував її для задач на пʼятнадцятий день!

На початку було дуже складно, зокрема через відсутність навичок роботи з реплом та редагування лісп-коду. «Кляті дужки!» 😂 Без paredit погано, з paredit погано — про це окремо ще розповім.

Одним із сюрпризів став той факт, що на ліспі взагалі-то можна (і дуже легко) писати імперативно. Чогось був переконаний, що це чисто ФП-шна штука на кшталт Haskell. Тож тут одразу +1 до приємності.

У Janet також є фішка з підтримкою PEG, якою вони надихнулися зокрема з REBOL/Red. Для задачі воно було непотрібно — можна було тупо працювати з charʼами, але мені кортіло спробувати, тож я запарсив файл отакою граматикою:
(defn parse-input [input]
(def grammar
'{:wall (/ "#" :w)
:empty (/ "." :e)
:box (/ "O" :b)
:robot (/ (* (line :row) (column :col) "@") :r)
:cell (+ :wall :empty :box :robot)
:line (* (group (some :cell)) "\n")
:grid (group (some :line))

:up (/ "^" :up)
:down (/ "v" :down)
:left (/ "<" :left)
:right (/ ">" :right)
:direction (+ :up :down :left :right (? "\n"))
:moves (group (some :direction))

:main (*
:grid (-> :row) (-> :col)
"\n"
:moves)})
(let [[grid row col moves] (peg/match grammar input)]
[[(dec row) (dec col)] grid moves]))

Через наявність PEG у Janet немає підтримки регулярних виразів, бо в принципі вони непотрібні. Я не певен, чи це добре: все-таки інколи фігачнути маленький regexp швидше й легше, але ок. Іншим мінусом Janetʼівського peg/match є той факт, що вона працює тільки з рядками на вході. Якщо порівнювати з ріболівським parse, це сильний недолік, адже там вони прям блоки коду (а код — це дані) парсять тим же самим парсом. Це логічніше, бо яка різниця, що ви отримуєте на вхід: список текстових літер чи список кейвордів? Через це нові діалекти в REBOL робити легше й приємніше.

Із загальних приємностей Janet: є fibers, threads, channels, streams тощо — все, що потрібно, є. Також доволі розвинена стандартна бібліотека, менеджер пакетів, FFI взаємодії з сішним кодом.

До синтаксису звикаєш значно швидше, ніж здалося на початку. А ось до назв функцій трохи повільніше. Мій чинний код стопудєй максимально далекий від ідіоматичного і ще далі від оптимального, але я отримав задоволення, доки писав!

Другу задачу, яка фактично є ускладненою версією гри Sokoban, я так і не доробив трохи. Коли починав, то прийняв не найкращі рішення щодо структур даних, потім нахуєвертив зверху, задобався з цим боротися, та й загалом дуже втомився. На цьому мій Advent of Code завершився.

Я ще згодом напишу підсумки про саму подію, а зараз можу впевнено сказати, що Janet — це одна з моїх нових улюблених мов!
Please open Telegram to view this post
VIEW IN TELEGRAM
Трохи підібʼю підсумки Advent of Code, який для нормальних людей закінчився три дні тому, а для мене ще раніше. Сьогодні з трошечки більш технічного погляду, а завтра — з більш філософського.

Я брав участь вперше, і мені дуже сподобалося! Взагалі-то я алгоритмічні задачки не дуже люблю. Алгоритми — мій слабший за інші бік, і так було завжди. У роботі мені вистачало базових знань відмінностей між стандартними контейнерами, а основні рішення були радше архітектурними, ніж алгоритмічними. Ті ж BFS/DFS я не писав власноруч років 15. А зараз вже багато років я взагалі не отримую грошей безпосередньо за написання коду, бо давно посідаю роль майстра на всі руки (technical product manager, що б це не значило).

Тим не менш, я спробував розвʼязати задачі в перший день, у мене вийшло, а далі вже складно було відірватися. Разом їх аж 50 штук — по дві на кожен день з початку грудня й до Різдва. З них я отримав 34⭐️, що вважаю непоганим результатом.

Прикол Advent of Code в тому, що він буде для вас настільки складний, наскільки складним ви самі хочете його зробити. Планка дуже низька, але ніхто не забороняє вам поставити особисті цілі: робити максимально короткі рішення, рішення з O(1) по памʼяті, розвʼязувати все в якомусь Excel чи COBOL — на ваш смак!

Якось так вийшло, що я вирішив писати щодня різними мовами програмування, багато з яких використовував уперше. Тому код мій здебільшого максимально тупий — аби працювало. Коли можна написати брутфорс, я його писав, хоча з другого тижня задачі пішли складніші, і повний перебір вже рідко давав результат.

Згодом мені неодноразово допоміг своїми порадами та поясненнями суперкрутий пан @ololocat, та й у принципі тримав мене в тонусі весь цей час, проте, до кінця я все-таки не дотягнув 🥲

За весь період я писав на Haskell 💻, io 💻, Nu 🆕, Swift 🕊, Python 💻, Red 🔺, C++ 💻, Nim 👑, Go 💻, Elixir 💻, Dart 💻, SWI-Prolog 🦉, Ada 💻, Janet 👩‍🦰, Crystal 🔮, Lua 💻 (і Terra) і PHP 🕸, отримавши багато корисного досвіду. (Не встиг спробувати: Pharo, Racket, F#, Uiua, Gleam, Zig).

Найцінніший «інсайт» (а точніше загальновідомий факт, про який писав ще Ніклаус Вірт у своїй книжці) — структури даних, які ви обираєте для своїх задач не менш важливі (я б сказав: значно важливіші) за алгоритми. За цей місяць я відчув це максимально яскраво: коли на початку обрав погану структуру даних, то рухатися далі вкрай боляче, а коли структура даних норм, то й алгоритм ллється наче пісня.

Якщо хтось захоче повторити мій шлях зі швидким опануванням якоїсь мови програмування (не треба), то ось кілька порад:
• Якщо є офіційний туторіал від авторів мови, я читаю його. Вони зазавичай знають, з чого почати й у якій послідовності рухатися.
• Допомагає Learn X in Y minutes. Бували випадки, коли коротка стаття тут давала більше корисного, ніж офіційна документація.
• Дуже стає в пригоді ШІ-шка. Жодний з алгоритмів, який я просив її зробити, вона не зробила правильно, але в питаннях «як прочитати файл» або «як передати функцію у функцію» — неоціненний помічник на початку.
• В рідких випадках довелося читати книжки.
• Ну й краще мати попередній досвід зі схожими мовами 😅 Але про це докладніше вже завтра.
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
Тож повернемося до списку мов з попереднього допису. Розділю їх на категорії:

Проходняк (суто для мене, бо особливих інсайтів не отримав):

- C++, Python — мої дефолтні мови наразі. Найчастіше пишу ними, але буду зсуватися в бік наступного списку.
- Nim, Go — перша загалом норм, але мені нема, що нею писати. Друга інколи стає в пригоді на роботі, хоча синтаксично не дуже зайшла.
- Dart, Crystal, PHP¯\_(ツ)_/¯
- Lua — варто знати, але знати там нема шо.

Практичні:
- Nu — моя повсякденна мова в оболонці, якою я користуюся буквально щодня. Проста, практична й приємна.
- Swift й Ada — обидві є моїми кандидатами на заміну C++ для мікроконтролерів. Перша хіпстерська, друга зі спадщиною. Поки не вибрав — думаю, що користуватимуся обома потроху.
- Elixir — наче Erlang з приємним синтаксисом. Використовуватиму.
- Janetмій перший лісп. Варто було б додати до концептуальних, бо тема про оперуванням кодом як даними, макроси й оце все — це дуже цікаво й важливо. Але я ці концепції вже збагнув, коли знайомився з Red, тому тут приблизно знав, чого очікувати. Хотів якусь таку мову, щоб ще й збиралося в один бінарь, і ось маю.

«Концептуальні»:
- про Haskell я вже сказав.
- Далі io, яка колись давно допомогла мені трохи краще побачити «справжнє» ООП, хоча, мабуть, краще б я спробував Pharo.
- Red — можливо, найособливіша мова з усього списку, але трохи просрана (писав тут і тут).
- Prolog — це мій новий краш ❣️ З ним я знову почуваю себе початківцем (хоча знання Haskell допомагає). Вважаю, що розібратися з логічним програмуванням — абсолютний маст хев.

Наразі все. Бажаю всім гарного Нового року. Я ж трохи збавлю темпи написання сюди — треба відпочити.
Please open Telegram to view this post
VIEW IN TELEGRAM
У цей вечір послухайте MIDI-версію «Щедрика». Для багатьох людей це, можливо, різдвяна пісенька з «Один вдома», але насправді написана вона була більш ніж століття тому українським композитором Миколою Леонтовичем, тоді як її коріння взагалі йде десь з дохристиянських часів України.

А взяв я її в одному з моїх найулюбленіших українських каналів SnC Museum Lab, і ніяк не можу збагнути, чому там так мало підписників.

Вітаю всіх з прийдешнім Новим роком!
Якщо хтось раптом думав теж вивчити 🦉 Prolog за моєю порадою, то маю для вас отакий список посилань на різні матеріали. Спочатку збирав його власноруч, потім дружбан докинув туди ще купу PDF-ок.

(А найкраща подяка з вашого боку — це репост 😉).
Please open Telegram to view this post
VIEW IN TELEGRAM
Зараз вже важко собі уявити програмування без використання вказівників на ділянки памʼяті. Навіть якщо ваша мова не дає вам працювати з адресами напряму, ви певно все одно знаєте, що «десь там» у трансляторі вони скоріш за все є. Появу вказівників у мовах програмування високого рівня зазвичай повʼязували зі створенням мови PL/1, а чуваку на імʼя Гарольд Ловсон навіть присудили за це Computer Pioneer Award від IEEE у 2000 році.

Але днями я дізнався, що ще у 1955 році, на 9 років раніше за PL/1, українка Катерина Ющенко створила Адресну мову програмування, яка вже мала цю концепцію.

До речі ця мова високорівнева, тобто не привʼязана до конкретного заліза, що дозволило успішно її використовувати на багатьох радянських компʼютерах тих часів. І це робить її однією з найстаріших мов високого рівня. Ті ж ALGOL та Fortran, схоже, зʼявилися пізніше, наприклад. (Першою мовою високого рівня вважається Plankalkül Конрада Цузе, створений у 1940-х, але реальна імплементація зʼявилася лише в 1970-х).

У прикладі коду на зображенні (це просто рандомний шматок) цікавий також оцей запис: ²d3. Оця двійка на початку — це замінник двох штрихів, а штрих — це розіменування вказівника. Тобто в мову вже були закладені вказівники вищих порядків. Дуже круто.

Окрема історія — як я про це дізнався. Дружбан, з яким я патякаю про Prolog, скинув мені статтю про Meta. Я почав гуглити, що значить --> у пролозі й вийшов на дивний український ютуб-канал, де якісь викладачі й студенти щось парять про Prolog. А там побачив згадку Катерини Ющенко й вирішив почитати докладніше.
This media is not supported in your browser
VIEW IN TELEGRAM
Люблю інколи поспостерігати за іншими, щоб побачити на їхньому прикладі, як легко все проїбати. Тут «все» може значити дуже різні речі звісно.

Ось торік Sonos — виробник доволі класних (мені подобаються) і недешевих бездротових колонок — вирішили переписати свою прогу на телефони з флаттера на флаттер 💻. Релізнули її в сирому вигляді попри застереження розробників. Половина фічей, що були доступні раніше, зникли, у багатьох користувачів були проблеми з підʼєднанням тощо. В результаті: −16% прибуток, −13% ціна акцій, −100 людей через скорочення, −1 CEO (пішов у відставку), −1 CPO (Chief Product Officer — пішов з компанії). Я вже колись писав, що компанії, які роблять чудове залізо, зазвичай роблять вельми кепський софт. Ось зайве підтвердження.

А останніми днями вже у світі 3D-друку трагедія. У мене принтера немає, але темою цікавлюся, тож зараз і вам розповім.

Є короч китайська Bambu Lab, яка робить дуже юзер-френдлі принтери — майже «apple світу 3D-друку». Ними захоплюються, всі їх радять новачкам, бо по співвідношенню ціни і якості вони класні. А у тих, хто будує на цьому бізнес, взагалі ферми по 60 і більше принтерів. До того ж існують моди, додаткові пристрої, спеціалізований софт тощо — все для того, щоб зробити ці принтери ще кращими. Іншими словами — чудова інвестиція грошей. Була.

Була, бо хтось у Bambu певно подумав, що гроші проходять повз, втрачені продажі (aka «недоотриманий прибуток») і ось це все. Тож вочевидь вони вирішили вендор-локнутися, причому традиційно під прикриттям турботи про безпеку. Пишуть, мовляв, вам прилетить апдейт на прошивку, після встановлення якого весь 3rd-party софт для керування принтером іде нах, відкритий протокол MQTT іде нах, перегляд стріма з камери йде нах, LAN-мод іде нах, бо тепер треба створювати обліковий запис і авторизуватися в їхній хмарі. А якщо прошивку не оновити, то принтер в якийсь момент може відмовитися друкувати. Я прям уявив, як в цей момент у них на мапі світу з підʼєднаними принтерами в якійсь графані потухла половина планети 😂

Звісно у людей миттєво згоріли сраки. Ютубери запустили конвеєр відосів про те, як вони помилилися щодо попередніх порад продуктів цієї компанії і все таке. В українському сегменті інтернету це б назвали «провезти шакалячим експресом». На редіті всі кинулися писати, що продають свої 100500 принтерів і переходять на Prusa (інший відомий виробник).

Потім виявилося, що для роботи з 3rd-party софтом Bambu все-таки лишила можливість — додаткову прогу Bambu Connect (на електроні), яка пропонує enhanced security . Тобто напряму принтером керувати все-таки вже не можна, але можна закидувати в цю прогу інструкції, а далі вона сама. Звісно ж менше ніж за добу хтось відреверсив її, збагнувши, як діставати приватні ключі для авторизації 🤡

Чотири дні потому Bambu Lab «відреагувала на ситуацію». Шо, думаєте вибачилися й сказали, що все повертають, як було? Звісно ні 🙁 Натомість вони написали: «Ви все не так зрозуміли». А щоб звучало переконливіше, відредагували попередній допис, а початкову версію дропнули з Wayback Machine 🫠 Тупо газлайтинг. Особливо кричу з їбаньків, які виправдовують їхні дії казками про безпеку через завʼязування на хмару (коли до того принтер навіть в інтернет не дивився).

Ну короч, не думаю, що вони прям втратять свій бізнес через це. Скоріш за все поточні користувачі підуть, а нові прийдуть вже на інші умови, з якими миритимуться. Але який же треш 🤪
Please open Telegram to view this post
VIEW IN TELEGRAM
Колись давно я відкрив для себе Obsidian, і відтоді користуюся ним регулярно. Навіть цей допис у ньому пишу. Я не намагався побудувати в ньому базу всесвітніх знань — радше юзаю як зручний редактор. Раніше для цього у мене був OneNote, однак, нині мені більше імпонує можливість зберігати все у маркдауні ⬇️.

А також певний час назад я дізнався про існування MkDocs — генератора статичних сайтів з документацією, який теж працює з Markdown. Для нього ще існує дуже відома й популярна тема Material for MkDocs. Я особисто гугловий Material Design зневажаю, бо вважаю його несмаком, ще й погано продуманим. Але тема для MkDocs зроблена настільки якісно й має таку кількість додаткових фічей, що складно її ігнорувати.

«Використовуватиму Obsidian як редактор документації під MkDocs», — подумав колись я. Та річ у тім, що Markdown сам по собі доволі бідний, тож для додаткових фічей усі вигадують якийсь власний синтаксис, часто несумісний, щоб робити виноски, вбудовувати відоси, додавати застереження тощо.

Тож я сів і навчив MkDocs [частково] розуміти синтаксис Obsidian (про що згадував отут й отут). Додав пару тижнів тому туди підтримку так званих callouts.

Я взагалі ніде, окрім як тут, не афішував цей плагін, але потроху-потроху — і вже назбиралася 41⭐️. It ain't much, but it's honest work.
Please open Telegram to view this post
VIEW IN TELEGRAM
Коли я тільки-но починав програмувати, то з величезним задоволенням писав на QBasic, а потім на Pascal. Згодом перейшов на 💻, яка вражала лаконічністю, та водночас мала в порівнянні певні незручності. Наприклад, ті ж нуль-терміновані рядки.

Але найбільше на початку дратувала робота з багатовимірними масивами. Якщо в тому ж паскалі можна дати змінній тип, наприклад, двовимірного масиву, щоб потім звертатися до його елементів як pixels[x,y], то в сішці таке не спрацює, і треба писати pixels[x][y].

Нічого страшного начебто — просто трохи інший запис. Але ця фішка перейшла і в 💻. А там доволі швидко зʼявляється бажання загорнути «сирі» масиви в якийсь клас з типобезпечнішою апішкою. Ну там матрицю ту ж зробити чи тензор якийсь, поперевантажувати оператори, щоб зручніше було, тощо.

Уявімо, що написали ви свій клас Matrix. Всередині у вас лежить якийсь масив; треба тепер визначити operator[], щоб можна було читати/писати конкретний елемент. Якби була багатовимірна індексація, то це суперлегко, причому дані всередині можна взагалі тримати в одновимірному масиві, а індекс обчислювати. Однак matrix[i][j] — це дві окремі операції, які можна записати як (matrix[i])[j]. Іншими словами, matrix[i] має повернути щось, на чому потім можна викликати .operator[](j).

Що це може бути? Ну, по-перше, можна повертати тупо вказівник на початок рядка, проте, я не почуваюся комфортно з цього приводу, бо забагато свободи: можна за межі піти, можна зберегти у змінну кудись, а потім навіть не дізнатися, що він інвалідувався (бо не володієш ним) тощо. При обережному використанні працюватиме, але наша мета ж знизити когнітивне навантаження, а не навпаки.

По-друге, можна повертати якийсь проксі-обʼєкт власного типу на кшталт Matrix::Row. Зробити, щоб створювати його могла тільки сама Matrix, по максимуму обмежити допустимі операції, перевизначити operator[]… Головняково якось, ще й сумнівно з погляду на швидкодію.

Натомість зазвичай перевантажують не індексацію, а операцію виклику функції operator(). Тоді є змога вказати довільну кількість параметрів. Але використання вже не настільки файне на вигляд, бо дужки круглі: matrix(i, j). Окрім неможливості в такому випадку зробити одночасно й індексацію, і виклик обʼєкта як функції (що все одно навряд чи комусь знадобиться), мені в цьому не подобається, що воно складніше сприймається. Око вже звикло, що квадратні дужки — доступ до елемента, круглі — виклик функції, тому натренований токенайзер у мозку даватиме хибні результати й сповільнюватиме читання й розуміння коду.

В інших, кращих мовах багатовимірна індексація вже присутня. Не тільки в спеціалізованих на кшталт 💻. Наприклад, у 💻 є, по-перше, багатовимірні масиви базових типів (int[,]), а по-друге, кастомний індексатор може приймати більше одного параметра: this[int row, int column]. (Так, мені дійсно подобається C# місцями). У Python 💻 метод __getitem__ завжди приймає тільки один параметр, проте, коли ви пишете matrix[i,j], то оці значення через кому запаковуються в кортеж, тобто викликається Matrix.__getitem__(matrix, (i, j)).

Щодо C++ же я давно змирився, що зручно тут не буває. І, уявіть собі, дарма!

Короч, починаючи з C++23 тепер є багатовимірна індексація, тобто це вже працює. Тепер operator[] може приймати довільну кількість параметрів різних типів:
m[0, 1, "shiii~"] = 42;

І до купи цей оператор тепер ще й статичним може бути.

Не вау яка фіча, і все ж мені до душі.
Please open Telegram to view this post
VIEW IN TELEGRAM