tgoop.com/cpplastic/365
Create:
Last Update:
Last Update:
За порадою пана @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
[email protected] eq([4, 6, 3, 5, 6, 3, 5, 2, 1, 0])
end
end
Запускаєш
crystal spec
, і воно все перевіряє й видає результат. Дуже зручно, бо не треба якісь окремі ліби підʼєднувати чи щось налаштовувати — воно одразу є при створенні проєкту.До речі щодо створення. Вельми непоганий тулінг у Crystal! Взагалі не було з цим проблем.
Короч, підсумок: мова наче й непогана, компілюється, перевіряє типи, працює явно швидше за Ruby, проте, з іншого боку не зовсім ясно на кого вона орієнтована. Хто і для чого її взагалі використовує? Є тут хтось, хто пише нею?
P.S. Усіх з Різдвом, до речі