Uploaded avatar of xavdid

Роздуми про #12in23 від того, хто пройшов його до кінця

@xavdid
Більше 2 років тому

Цей допис спершу опубліковано на сайті Девіда і передруковано тут із дозволу

Минулого січня Exercism оголосив нову програму під назвою 12in23, у якій запропонував учасникам спробувати 12 нових мов програмування у 2023 році. Кожен місяць мав власну тему (як-от «Аналітичний квітень» чи «Обʼєктно-орієнтований жовтень») і висвітлював конкретні мови для спроби. Я люблю вчити щось нове й уже став трохи (програмним) мовним гіком, тож вирішив спробувати. 12 мов, 12 місяців!

Тепер, коли рік майже минув, я неймовірно задоволений тим, як усе склалося. Мені вдалося спробувати 12 нових мов, познайомитися з чудовими людьми в спільноті Exercism і зробити кілька класних внесків у відкритий код! У цьому дописі я розповім про всі мови по черзі й про те, що кожна з них мені дала.

Вибір мов

Я окреслив кілька орієнтирів на рік, щоб отримати від цього досвіду якнайбільше:

  1. Мови мають бути або цілком новими для мене, або принаймні достатньо незнайомими, щоб я відчував, що багато вчуся.
  2. Вибрані мови мають бути (потенційно) практичними, щоб вивчати їх і далі. Цей проєкт був просто для розваги, але я хочу витрачати час на (хоча б частково) корисні речі.
  3. Для кожної мови, яку я використовував, я встановлював усі локальні інструменти й плагін для VSCode. Хотілося порівнювати мови приблизно в рівних умовах, із якомога більшою кількістю підказок типів і IntelliSense. У коледжі я робив усі домашні завдання з програмування в Sublime Text без жодного лінтера чи автодоповнення. Я боявся, що якщо навчуся програмувати з усіма цими інструментами, то надто покладатимуся на них і не стану добрим програмістом. Сталося якраз навпаки. Чим більше мислення я можу перекласти на свої інструменти, тим більше можу думати про саму задачу. Не запамʼятовуймо речі, а запамʼятовуймо, як їх знайти.

Нумо до справи!

Січень (без теми)

Коли почався січень, команда Exercism ще обирала теми на місяці, тож мову для того місяця можна було вибрати на власний розсуд. Без жодного напрямку я почав рік із Go. Я пройшов інтенсивний курс із цієї мови в середині 2022 року, але відтоді майже не користувався нею і зовсім не почувався впевнено.

Go цікава мова. Її суворий компілятор означає, що програма буде правильною, і ми не зрушимо з місця, доки він не вирішить, що це безпечно.1 Її багатослівний підхід до обробки помилок означає, що нас ніколи не застають зненацька (ціною того, що if err != nil { return err } доводиться писати дуже дуже багато разів). Вона добре робить складні речі простими (наприклад, паралелізм через канали), але й ускладнює деякі прості речі, як-от роботу з рядками тексту (англ. string). У неї надійна стандартна бібліотека, тож більшість завдань можна виконати без сторонніх модулів. Мені подобається, що значна частина екосистеми (форматування, встановлення, збірка тощо) належить самим авторам мови й вбудована в команду go. У мови є критики, але, гадаю, вона здебільшого досягає своїх цілей: правильності й зручності підтримки.

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

Функціональний лютий

Лютий одразу пірнув на глибину з функціональними мовами, які є математичним відгалуженням звичніших імперативних мов програмування. Функціональні мови відомі своїми «чистими» функціями (без побічних ефектів). Я обрав Elixir переважно тому, що мій друг Кейлеб використовував його для Advent of Code і дуже його хвалить.

Мені дуже сподобалося працювати з Elixir. Його надихнув Ruby (що логічно: його творець, Жозе Валім, був одним із ключових розробників Rails). Функціональні концепції, як-от ланцюжки методів, виражалися просто. Мені сподобався весь синтаксичний цукор, який це полегшував, зокрема оператор конвеєра (|>):

foo(bar(baz(new_function(other_function()))))
# becomes
other_function() |> new_function() |> baz() |> bar() |> foo()

Це також був мій перший досвід роботи з макросами, тобто кодом, який пише код. Оскільки програми на Elixir можна виразити у AST, яке саме є валідним кодом Elixir, легко писати код, що породжує інший валідний код. Дуже класна концепція, яку Elixir зробив простою. Мені також зайшло, як функції могли зіставляти зі зразком форму своїх аргументів, тож виклики функцій спрямовувалися до відповідної реалізації:

defmodule TuplePrinter do
  def print({a}) do
	IO.puts("single")
	IO.puts(a)
  end

  def print({a, b}) do
	IO.puts("double")
	IO.puts(a)
	IO.puts(b)
  end
end

TuplePrinter.print({1})
TuplePrinter.print({2, 2})

# single
# 1
# double
# 2
# 2

Це схоже на ту особливість, яка або чудова, або перетворює код на суцільні спагеті. Хай там як, концепція класна!

Elixir також виграє від того, що працює всередині віртуальної машини BEAM від Erlang, що дає їй велику екосистему для взаємодії. Вона чудово працює з паралелізмом і лежить в основі улюбленого вебфреймворку Phoenix.

Хоча прямої потреби використовувати Elixir у мене немає, працювати з нею було дуже весело, і я цілком готовий повернутися до неї. До того ж було цікаво розвʼязувати знайомі задачі незнайомими способами (а саме рекурсивно).

Механічний березень

Березень присвятили «системним» мовам, які компілюються в машинний код.

З-поміж варіантів мені була цікава лише Go.2 Уважні читачі помітять, що я вже присвятив Go цілий місяць, тож він не зарахувався б як одна з моїх 12. Річ у тім, що коли я вибрав його на січень, теми ще не оголосили, тож я не усвідомлював, що сам загнав себе в кут.

Якби я знав, що зʼявиться Bun, то, ймовірно, спробував би Zig, але, на жаль, я не вмів (іще) передбачати майбутнє. Тож, за відсутності переконливішого вибору, я взяв ще один місяць Go, розуміючи, що десь пізніше того року доведеться взяти дві мови за один місяць.

Аналітичний квітень

Квітень був присвячений мовам, популярним у науці про дані. З Python я був надто добре знайомий, а R проходив на курсі статистики в коледжі (і не злюбив його), тож вибір упав на Julia!

Мені сподобалося працювати з нею, але здебільшого тому, що вона відчувалася дуже схожою на Python. Це трохи бентежило, наче бути американцем у Канаді. Усе здається дуже знайомим, але лише трохи не таким, і годі зрозуміти, в чому саме річ. Раптом нам пропонують монету в 2 долари (або функцію, яка справді добре пасує для матричних обчислень), і ми розуміємо, що вже не в Канзасі.

Найбільше мене вразила система типів Julia. Вона була анотована (необовʼязково), як і система типів Python, але мала перевірки під час виконання, які гарантували, що аргументи відповідають оголошеним типам. Гадаю, система Python вдало балансує між інтеграцією з інструментами й тим, щоб не заважати, але визнаю: помилки часу виконання в Julia для функцій із неправильними типами теж були корисними.

Зрештою, Julia була гарною, але навряд чи знадобиться мені в майбутньому.

Травень зміни мислення

Травень подвоїв ставку на «спробуй щось нове», висвітливши мови, які роблять дуже незвичні речі. Я скористався нагодою й спробував надпопулярний Rust. Мушу сказати, я розумію, чому всі так ним захоплюються.

Хоча до сумнозвісного перевірника позичань справді треба звикнути, мені сподобалося, як він змушував мене думати про свої програми уважніше. Компілятор був неабияк суворий, але його повідомлення про помилки допомагали виправляти проблеми краще, ніж можна було сподіватися. Не скажу, що першого тижня я був особливо продуктивним, але принаймні бачу верхівку кривої навчання.

cargo, менеджер пакунків Rust, теж заслуговує на окрему згадку. Хоча я не встановлював жодних сторонніх пакунків, його збірка, тестування й форматування були чудовими. Те саме стосується розширення для VSCode, у якому було все, чого я очікую від статично типізованої мови, як-от Rust. Добрий досвід розробника справді змінює все.

Хоча на рівні реалізації вони дуже різні, за призначенням Rust відчувався схожим на Go: і той, і той роблять програми дуже швидкими. Багато інструментів у мовах, якими я регулярно користуюся, починають переходити на Rust заради його швидкодії, тож я очікую бачити його частіше (навіть якщо сам не писатиму код на Rust).

Літо S-виразів (червень)

Червень був місяцем S-виразів, поширеної синтаксичної форми в ліспах. Я обрав Clojure, функціональну мову, що працює на JVM.

Багато років тому я трохи писав на Clojure. Я щойно закінчив навчання й на першій роботі став єдиним супровідником критичного для бізнесу щоденного скрипту. Зайве казати, це був непростий час. Мені було цікаво перевірити, чи тепер, коли я став старшим і мудрішим, вона стала доступнішою.

Радий повідомити, що стала! Функціональний досвід із лютого допоміг мені думати рекурсивно, а синтаксис виявився не таким уже й страшним, коли в нього зануритися. Сумісність із JVM теж стала б у пригоді, якби я використовував Clojure у більшому проєкті.

Не бачу, щоб я колись використовував Clojure для чогось, коли є альтернативи, але досвід був не зовсім неприємний.

Побічний квест: Універсальний запускач тестів!

Роками я використовував маленьку bash-функцію, щоб запускати модульні тести в поточній теці. Працюючи з усіма цими новими мовами, я раз у раз додавав до неї рядки для зручності: згадати про t було набагато легше, ніж щоразу заново вчити специфічну для мови команду тестування.

Коли потрібна логіка переросла мій рівень комфорту з bash, я витратив трохи часу в червні й виокремив проєкт у щось самостійне: Universal Test Runner.

Я поділився ним на форумі Exercism, і його добре прийняли. Він сподобався настільки, що ми вирішили вбудувати подібну функціональність у сам CLI Exercism (написаний на Go, а це тема, яку я, на щастя, саме освіжив). Тож у другій половині року я міг запускати exercism test, щоб прогнати набір тестів для мови того місяця (команда, яку Universal Test Runner підтримує з коробки).

Якщо хочеться дізнатися більше про цей процес, я набагато докладніше писав про нього коли він вийшов.

Гаразд, рухаємося далі!

Юрський липень

Липень показав старі мови. З погляду практичності цього місяця вибір був досить бідний. Я почав із шанованого COBOL, бо чув, що він досі керує чималою критичною інфраструктурою. Але насувалася весільна церемонія на початку серпня, і в мене не було снаги сісти й вчити мову, настільки не схожу на все, що я знав. Тож натомість я перейшов на Visual Basic, як найменш поганий варіант.

Тут небагато чого сказати. Мова здалася трохи багатослівною, але досить простою у використанні. Наскільки я розумію, її насправді створювали для розробки інтерфейсів користувача у Windows, тож на маленьких вправах важко скласти про неї повне враження.

Застосунковий серпень

Серпень був переповнений мовами для створення застосунків. Як і слід було очікувати, цього місяця вибір був багатий. Я взяв Swift. Оскільки я користуюся багатьма продуктами Apple, їхня власна мова мені цілком доречна. Вона не була для мене цілком новою: 2016 року я опублікував один застосунок для iOS, написаний виключно на Swift. Але відтоді не торкався цієї мови, а вона чимало змінилася, тож я вирішив, що це все ще зараховується.

Мене приємно вразило, як легко з нею працювати. На відміну від багатьох інших мов у цьому списку, Swift досить новий. Його вперше випустили 2014 року, і він явно скористався уроками сучасного проєктування мов. У нього є власний менеджер пакунків, необовʼязкові ланцюжки, функції першого класу й розумна інтерполяція рядків тексту. Читати й писати цією мовою було зручно, навіть без Xcode.

Загалом кажучи, Swift корисний переважно для застосунків на платформах Apple, які я зараз не пишу. Хоча для вправ він підійшов добре, повертатися до нього найближчим часом не планую. Зате мені дуже подобається, що його можна писати на iPad!

Мінімалістичний вересень

Вересень досліджував дуже лаконічні або маленькі мови. Я взяв jq, інструмент, який використовую й люблю вже багато років.

Щоправда, я завжди сприймав його лише як інструмент для роботи з JSON, а не як мову програмування загального призначення. Тож був приємно вражений, побачивши, що в нього є все звичне: функції, змінні, цикли тощо. Тобто я міг писати досить складні програми:

# input: { "series": "1", "sliceLength": 1 }
. as {series: $series, sliceLength: $sliceLength} |
if
  $series == "" then
	"series cannot be empty" | halt_error
  elif $sliceLength > ($series | length) then
	"slice length cannot be greater than series length" | halt_error
  elif $sliceLength == 0 then
	"slice length cannot be zero" | halt_error
  elif $sliceLength < 0 then
	"slice length cannot be negative" | halt_error
  else
	.
end
| [range(0; $series | length)]
| map($series[. : . + $sliceLength])
| map(select(. | length == $sliceLength))

Було цікаво спробувати всі можливості jq, які ніколи не були потрібні для простих перетворень даних. Хоча інструментарій тут трохи кульгає (немає інтеграції з редактором тощо), глибше розуміння широти можливостей jq було цінним.

ред.: Ді-Джей Адамс у Mastodon звернув мою увагу на проєкт jq-lsp і відповідний плагін для VSCode. Цього разу я його проґавив, але в майбутньому обовʼязково подивлюся.

Обʼєктно-орієнтований жовтень

Жовтень занурився в обʼєктно-орієнтовані мови. Я маю слабкість до обʼєктно-орієнтованого проєктування, яке добре відображає те, як я уявляю програми в голові. Я взяв Ruby, що може здатися дивним вибором.

Я працюю в Stripe, де розташована найбільша кодова база на Ruby у світі. Певно, Ruby не можна вважати «незнайомою» мовою? Усе це так, але наш Ruby-моноліт дуже далекий від «стандартного» Ruby: усе перевіряють типами через Sorbet, там багато генерації коду, і ми робимо чимало магії, щоб усе працювало разом і масштабувалося. Хоча Ruby всередині й поза Stripe зрештою та сама мова, робота в настільки різних масштабах дає зовсім різний досвід; мені хотілося дізнатися, як воно «назовні» (за роки відтоді, як я активно користувався Ruby).

Здебільшого було добре! Сам Ruby чудовий і називає «щастя програміста» однією з головних цілей, що мені відгукнулося. Мені подобається, як часто я можу вгадати назву функцій зі стандартної бібліотеки, якими ніколи не користувався. Подобається, як легко будувати функціональний код і який зручний та виразний у нього синтаксис.

Утім, мене здивувало, наскільки інструментарій для розробників відстає від Python. Можливо, мене розпестили, але підказки типів у редакторі та надшвидкий лінтер і форматування виявилися важливішими для мене, ніж я усвідомлював. Для мови, настільки популярної в її зеніті, як Ruby, було дивно, наскільки вона відстає в цьому плані.3 Я також ніяк не звик до необовʼязкових дужок у викликах функцій, через що передавати функції як аргументи було менш зручно.

Ruby досі чудова мова, і я й далі користуватимуся нею на роботі, але вона не робить для мене нічого такого, чого не робить Python, принаймні зараз.

Нібловий листопад

Листопад був найважчим місяцем: мови асемблера. Хоча писати їх вручну вже не прийнято, це корисна й цікава тема, з якою варто ознайомитися. Я вибрав WebAssembly через його важливість для сучасного й майбутнього вебу. Хоча зазвичай його використовують як ціль компіляції (а не як щось, що пишуть вручну), для диваків теж існує інструментарій.

Я почувався несподівано добре підготовленим до цього місяця. Синтаксис нагадував Clojure, а структура мови — TIS-100 від Zachtronics. Мені дивним чином сподобалося починати з нуля для кожної операції; це мало своєрідний шарм. Я б зненавидів це, якби доводилося справді щось робити в такий спосіб, але тим часом це була цікава забавка. Із щедрими коментарями мені вдалося написати щось майже читабельне:

(module
  (func (export "eggCount") (param $number i32) (result i32)
	(local $res i32) ;; result
	(local $remainder i32) ;; loop counter

	(loop $loop

  	;; $res =
  	(local.set $res
    	;; $res +
    	(i32.add
      	(local.get $res)
      	;; $number % 2
      	(i32.rem_u
        	(local.get $number)
        	(i32.const 2)
      	)
    	)
  	)

  	;; $number //= 2
  	;; (keep on stack)
  	(local.tee $number
    	(i32.div_u
      	(local.get $number)
      	(i32.const 2)
    	)
  	)

  	;; this will keep looping until remainder is 0
  	br_if $loop
	)

	local.get $res
  )
)

Найбільшою перепоною була нестача документації та ресурсів. Було важко навіть зрозуміти, які глобальні функції доступні. Але з огляду на те, що я насправді не збираюся цим користуватися, коли я розібрався, це мене не надто турбувало.

Грудень завершив рік мовами, які не вписалися в інші категорії. Через те що в березні я повторив мову, цього місяця мені треба було опанувати дві мови.

Я почав із Wren. Його створив Боб Ніструм, відомий, зокрема, книжкою Crafting Interpreters. Мене зачарували його увага до деталей, малий розмір і проєктування «згори вниз»; усе здається добре продуманим. Ця ретельність помітна в деталях областей видимості змінних і правил приватності. Його компілятор невеликий і рясно прокоментований, тож це чудове джерело для навчання тих, хто цікавиться реалізаціями мов.

Wren трохи шорсткий і, здається, здебільшого закинутий, але для іграшкової мови, гадаю, це нормально. Ніхто не береться за неї, очікуючи готовності до продакшену. У світі точно є місце для мов, не призначених для продакшену.

А ще: Lua

Другим моїм вибором цього місяця була Lua. На противагу Wren, вона надзвичайно практична. Її легко вбудувати, тож вона зустрічається в багатьох місцях, як-от скрипти для Redis і моди для Factorio. До обʼєктної моделі треба трохи звикнути, але, схоже, я швидко став би продуктивним. Мені швидко сподобалися таблиці як структура «на всі випадки». Інструментарій був добрий: менеджер пакунків працював з коробки, а розширення для VSCode без зайвого клопоту підтримувало анотації типів у коментарях.

Хоча зараз мені немає для чого терміново використовувати Lua, це ще один чудовий інструмент у скриньці завдяки її широкому вжитку.

Підсумки

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

Щодо того, що далі, то, гадаю, це глибше вивчення Rust. Його важливість у світі інструментів для розробників уже очевидна, і я хочу бути впевненим, що можу читати й доповнювати те, на що покладаюся.

Конкретного результату я не планую, але в мене є ціла книжка про Rust, курс «Rust для JS-розробників», який я колись оплатив із робочого бюджету, і цілий трек Exercism, який треба пройти. Хотілося б зробити внесок принаймні в один проєкт із відкритим кодом (ймовірно, Just, моя нова улюблена програма), але побачимо, куди мене заведе рік.

А поки що щасливих свят і гарного завершення 2023 року!

  1. Невикористані змінні спричиняють помилку компіляції?? Ну годі ↩

  2. Насправді я спершу спробував C++ (на якому не писав від коледжу). Просто було нецікаво, тож я його закинув ↩

  3. Це ще одна відмінність «справжнього» Ruby від мого досвіду в Stripe, тож я радий, що спробував обидва варіанти ↩

Translation missing: uk.number.nth.ordinalized Jan 2024 · Виявилося корисним?