Як я знову вчив Rust за допомогою ChatGPT та CodingBat

Трохи про себе: я вже не молодий розробник на Java. Починаючи зі старших класів школи, час від часу я програмував на Pascal, C, C++ і PHP, а з 2014 року працюю в ІТ-сфері як full-stack програміст на Java.

Перебуваючи у «тривалій творчій відпустці», я вирішив ближче познайомитися з Rust. Попередня спроба була років сім тому, і з того часу в пам’яті залишилися позитивні враження, але нічого більше.

План був такий: обрати книгу, почати її читати і водночас розв’язувати завдання на Rust, приблизно такого ж типу, як на LeetCode. Спочатку я обрав для себе переклад Rust in Action, але в якийсь момент відклав і продовжив із Програмування мовою Rust Д. Бленді та Д. Орендорфа.

Я визнав LeetCode таким, що теж не зовсім пасував: моє завдання було не покращувати навички розв’язання алгоритмічних завдань, а розвивати саме навички, пов’язані безпосередньо з мовою. Я пам’ятав, що колись за часів «java-юності», а згодом у період інтересу до Python, користувався ресурсом CodingBat. Звичайно, там немає розділу із завданнями на Rust, але я вирішив просто брати завдання з розділу Java і розв’язувати їх на Rust.

Було б ще непогано отримувати зворотний зв’язок на свої рішення — код-рев’ю, критику, поради тощо. Інтернет повний думок про те, що скоро (або вже) код писатиме ШІ, тому я припустив, що з перевіркою навчальних завдань ШІ точно має впоратися.

Від IDE я свідомо відмовився, щоб автодоповнення не заважало згадувати «а як це робиться на Rust». План був такий: я беру завдання підряд із розділу Java на CodingBat, розв’язую їх у Rust Playground, а після цього копіюю умову завдання і своє рішення до ChatGPT та пропоную йому висловитися з цього приводу. Обговорюю з ним зауваження, за необхідності вношу зміни та зберігаю остаточний варіант рішення до свого «архіву». Кількість таких ітерацій обмежувалася лише задоволенням результатом.

Якийсь час тому я закінчив цей «челендж». Усі завдання вирішені, книга дочитана, і загалом є деякі підсумки, якими я хотів би поділитися. Ці висновки стосуються моїх вражень від мови Rust, того, що було охоплено цим підходом, і того, що залишилося за бортом. Ну і, звичайно, вражень від використання ШІ, зокрема ChatGPT, у цьому контексті.

Rust: враження

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

Основна складність вивчення для мене (і багато хто з цим погоджується) — це «контроль запозичень» (Borrow Checking) і «час життя посилань». В інших мовах я з цими концепціями не стикався, а читання книжок не зробило їх цілком зрозумілими для мене. У спробах розібратися я також користувався ресурсом Rust Book (Cognitive Engineering Lab), який знаходжу корисним щонайменше для цієї мети. Наразі ситуація така: мозок уже не протестує, але помилки, пов’язані з цими концепціями, я роблю досить часто. Та й взагалі не стільки я володію «контролем запозичень» і «часом життя посилань», скільки вони володіють мною.

Мова досить нова, і, на мою думку, проблеми зі зворотною сумісністю не роблять розвиток мови та стандартної бібліотеки настільки важким, як, наприклад, у Java. Загалом речі лежать там, де мають. Наприклад, аналог Stream API знаходиться в ітераторах, а не прироблений десь збоку, як у Java (поширена думка про Stream API).

Патерн-матчинг та вираз match справили на мене ефект «золотого молотка»: часом здавалося, що їх хочеться використовувати скрізь і замість усього.

Потрібно відзначити певну складність роботи зі стандартними рядками у Rust. Для коректної роботи з рядками, що містять Unicode, потрібно розуміти, як вони зберігаються в пам’яті і що робить кожен метод рядкових типів.

У «безпечній» частині Rust немає null, і там, де в Java я б використовував null, у Rust доводиться застосовувати Option. У Java Optional здавався мені чимось надмірним. У Rust довелося змиритися: null більше немає. Думаю, що тепер і в Java я почав би дивитися на Optional інакше.

Завдання з CodingBat дозволили попрактикуватися з основними конструкціями керування виконанням програми (розгалуження, цикли тощо), базовими типами даних (знакові та беззнакові цілі, логічний тип, Option, стандартні рядкові типи, масиви, вектори, зрізи, діапазони, словники/карти), а також із тим, що я називаю «stream API».

З важливого, але не охопленого цими вправами, залишилася обробка помилок. Rust помітно відрізняється в підході до обробки помилок від інших мов, з якими я знайомий, зокрема від Java, і для роботи з ним важливо опанувати ці аспекти.

Також вправи не торкнулися трейтов та структур — частини того, що в інших мовах називається «об’єктно-орієнтованим програмуванням». У таких вправах їм важко знайти застосування.

І, звичайно, багато інших важливих частин мови та стандартної бібліотеки залишилися або не охопленими, або зачепленими лише поверхово.

ChatGPT: досвід використання

Цей варіант його використання, який я описав на початку, з’явився не відразу. Насамперед я, звичайно, спробував дати йому завдання і вимагати його розв’язання. Він справлявся з цим чудово. Точніше, цей засмучуючий експеримент був здійснений задовго до початку цього «челенджу». Тобто було зрозуміло, що він чудово справляється, а читання його коду мене не так щоб розвиває, а більш демотивує.

Тому я дійшов висновку: розв’язуватиму сам і даватиму йому на перевірку. Я просив його не надавати рішення, перш ніж я явно про це попрошу. Це було потрібно, оскільки кілька разів доводилося обговорювати з ним умову завдань чи ідею рішення, а він натомість одразу пропонував код, що сильно підривало навчальну цінність. Загалом при стандартній процедурі «самостійне рішення → передача на рев’ю» проблем не виникало, але при відхиленнях вимогу не надавати рішення потрібно було озвучувати явно.

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

Той ШІ, який ми зараз широко використовуємо, дуже добре допомагає, якщо ви добре розумієте проблему або завдання і можете його коректно сформулювати, використовуючи загальноприйняту в цій галузі термінологію. Він не вирішує завдання у тому сенсі, в якому це робить людина: ШІ шукає найбільш «ймовірне» продовження вхідного тексту. Якщо користувач недостатньо розуміється на темі і формулювання завдання неточне, результат ШІ може виявитися несподіваним. З іншого боку, завдання, подібні до LeetCode, та їхні розв’язання широко представлені в інтернеті і, ймовірно, часто використовувалися у наборах даних під час навчання ШІ.

Груба модель, якою я користуюся, щоб пояснити собі, як це працює, має такий вигляд: запит спочатку перетворюється на набір чисел — вектор. Цей вектор проганяється через величезний набір вагових матриць, отриманих у процесі навчання. У цих матрицях не зберігаються готові відповіді, а закладена інформація про те, як слова та фрази пов’язані одне з одним (у знаннях людства, представлених у наборі даних під час навчання; якби дані відображали уявлення кількох століть тому, модель могла б, наприклад, «переконувати», що Земля плоска).

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

Випадок 1: неефективне, але робоче рішення

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

Рішення вийшло особливо неефективним, але мене це на той момент влаштовувало. Тестові приклади із сайту проходили. Для більшої впевненості я вирішив дати це рішення на рев’ю ШІ, щоб дізнатися: а чи взагалі воно коректне? Мене цікавило лише одне — чи дає мій варіант правильні числові відповіді.

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

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

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

Випадок 2: «приховані вимоги»

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

Я з цим не погоджувався, але «хробак сумнівів» мене гриз. Зрештою я звернувся до конкурента — Gemini — і він повідомив мені, що існує дві інтерпретації цього завдання. Але та умова, яку я надав, дає впевненість, що вона відповідає саме моїй інтерпретації.

Загальний висновок

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

P.S.

Якогось моменту, щоб мій текст не був голослівним, я вирішив опублікувати свій «архів рішень». «Архів» містив файли з точкою входу (функцією main) і однією або кількома функціями, що реалізують рішення. Функція main друкувала в консоль результат виклику функції із заданими вхідними даними — такі собі ручні тести.

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

Перетворений «архів рішень» доступний за посиланням:
github.com/...​cotch02/codingbat_in_rust

І так, під час підготовки цього тексту ChatGPT теж використовувався.

Підписуйтеся на Telegram-канал «DOU #tech», щоб не пропустити нові технічні статті

👍ПодобаєтьсяСподобалось4
До обраногоВ обраному1
LinkedIn
Ctrl + Enter
Ctrl + Enter

В цілому бачу перспективу в схожій методології навчання. Теж прошу ШІ мені зробити ревью з рекомендаціями і без готових рішень. Сам робив чат бота для вивчання пайтона але якось він не дуже й то завдання давав, бо великого і красивого промпта недостатньо виявилось. Хотів зробити щоб він видавав завдання складніше и складніше з тим що я вже вивчив. Думаю треба після кожного чату робити міні самарі і його запамʼятовувати
Успішний кейс був коли я дав деякі умови Ші для генерації завдань по вивчанню sql: є fast api, таблиці авторів, книжок, звʼязків між ними, зроби мені пару завдань і вказував складність.

Що стосується SQL, якщо мета попрактикуватися саме в SQL, то я вважаю щось на зразок sqlzoo.net саме те, що потрібно для початку, підтримки або якщо потрібно освіжити забуте ...

Переклад розмови з ChatGPT: «Скажи якби я хотів використовувати твою допомогу при вивченні SQL, наприклад, для генерації та перевірки все більш ускладнюються тренувальних завдань SQL Який запит я повинен був би тебе направити?
ChatGPT сказав:
Чудовий запит! 🚀
Якщо ти хочеш, щоб я допомагав тобі вчитися SQL, то ваш запит до мене може бути дуже простим, але краще відразу задавати формат, який тобі потрібен.
Наприклад:
Якщо хочеш завдання для практики:
„Згенеруй тренувальне завдання SQL середньому рівню складності з таблицями і даними, і запитай, який запит потрібно написати.“
Якщо хочеш перевірити свій запит:
„Ось завдання і мій SQL-запит. Перевір, чи правильно він вирішує завдання, і якщо ні — поясни помилки.“
Якщо хочеш поступове ускладнення:
„Давай почнемо з простих SQL-завдань і поступово ускладнюватимемо. Ти даєш завдання, я пишу запит, ти перевіряєш і пояснюєш.“
Якщо потрібно щось конкретне (наприклад, робота з JOIN, GROUP BY, віконними функціями):
„Дай мені завдання SQL з використанням віконних функцій.“
⚡ Універсальний запит, який дозволить побудувати системне тренування:
„Хочу тренуватися в SQL. Давай так: ти генеруєш завдання з таблицями та умовами (спочатку просте, потім складніше). Я пишу запит, а ти перевіряєш і пояснюєш, чи правильно вирішив. Почнемо з простого рівня.“
Хочеш, я прямо зараз складу тебе перше просте тренувальне завдання (наприклад, робота з вибіркою SELECT, фільтрацією WHERE)» Але далі я не став пробувати :-)

Вибачте, не вчитався що SQL — вирішене питання :-)

З досвідом с++ не мало би бути проблем з лайфтаймами і концепцією володіння та move семантикою. А який промпт був для аналізу рішення? Типу ось завдання, ось код, проаналізуй як можно покращити, але не давай готове рішення. Чи бачив rustlings?

Дякую за активність. 1) Щось на кшталт 2) Так

Підписатись на коментарі