Парадигми програмування, які змінять ваше відношення до кодингу [*]


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

Конкурентність за умовчанням

Приклади мов: ANI, Plaid.

Припустимо, у нас є три рядки коду :

A;B;C;

При послідовному програмуванні ці рядки виконувалися б по черзі: спочатку A, потім B, потім C. Такий принцип за умовчанням реалізований у більшості мов, і ми звикли проектувати, тримаючи його в голові. Але в мові ANI усі три рядки є конкурентними, тобто можуть виконуватися в пересічних проміжках часу, у тому числі й паралельно.

Примітка Терміни “конкурентність” та ” паралелізм” часто плутають і намагаються ототожнити, що з’ясовно зважаючи на тісний зв’язок між поняттями, але це в корені невірно. Стаття про паралелізм у Вікіпедії містить наступне пояснення:

Нерідко плутаються терміни “паралелізм” і “конкурентність”. Обидва терміни означають одночасність процесів, але перший – на фізичному рівні (паралельне виконання декількох процесів, націлене тільки на підвищення швидкості виконання за рахунок використання відповідної апаратної підтримки), а другий – на логічному (парадигма проектування систем, що ідентифікує процеси як незалежні, що у тому числі дозволяє їх виконувати фізично паралельно, але в першу чергу націлене на спрощення написання багатопотокових програм і підвищення їх стійкості).

Остаточно розібратися в термінології допоможе наша стаття про конкурентність і відео “Concurrency Is Not Parallelism”:

Але повернемося до ANI, який реалізує конкурентність за умовчанням. Порядок виконання в даному випадку є побічним ефектом явно заданих залежностей між рядками коду: якщо B утримує посилання на змінну, визначену в A, то A і C можуть виконуватися у будь-якому порядку, а B – тільки після того, як завершиться виконання A.

Подивимося на приклади коду. У керівництві по мові говориться, що програми на ANI складаються з ” каналів” (pipes) і “клапанів” (latches), які використовуються для управління потоками даних. Синтаксис незвичайний і складно парситься, та й взагалі можна вважати, що мова пару років назад остаточно пішла у світ інший. Але ми тут із-за цікавих концепцій, тому продовжимо огляд.

Ось привіт світу:

"Hello, World!" ->std.out

У термінології ANI, ми відправляємо об’єкт "Hello, World"! (рядок) у потік std.out. А якщо туди ж відправити ще один рядок?

"Hello, World"! ->std.out
"Goodbye, World"! ->std.out

Будучи конкурентними і технічно незалежними один від одного, ці рядки можуть вивестися в консолі у будь-якому порядку. Тепер визначимо змінну і пошлемося на неї:

s =[string];"Hello, World!" ->s;s ->std.out;

Перший рядок оголошує “канал з клапаном” (він чимось схожий зі змінною) під назвою s, який містить рядок і клапан . Далі "Hello, World"! посилається в s, а третій рядок як би “відкриває” s і пускає вміст в std.out. Оскільки кожен рядок залежить від попереднього, програма виконається в тому ж порядку, в якому вона написана.

В цілому і конкурентність, і паралелізм – вже давно привабливі і активнорозвиваючи концепції: їх ефективна реалізація веде до сильного підвищення продуктивності. Тільки завдання це складне, а тому цікаво, чи може конкурентність її спростити (хоч би в теорії).

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

Залежні типи

Приклади мов: Idris, Agda, Coq.

Швидше за все, ви звикли до систем типів у таких мовах, як C або Java, де при перевірці компілятор визначає, чи є змінна цілим числом, списком або рядком. Але що, якби компілятор зміг перевірити змінну на відповідність типам “позитивне ціле число”, “список довжини 2” або “рядок-палиндром”?

Це питання приводить до розгляду мов програмування, в яких реалізована підтримка залежних типів, тобто можливість визначати типи, перевіряючі значення змінних на стадії компіляції. За прикладом можна звернутися до Scala і бібліотеці, що підключається shapeless, яка з експериментального проекту за декілька років виросла до відносно стабільного інструменту.

Оголосимо структуру Vector, що містить значення 1, 2 і 3:

val l1 = 1 :#: 2 :#: 3 :#: VNil

Код вище створює змінну l1, для якої визначення типу вказує не лише на те, що це Vector з цілими числами Ints усередині, але і на те, що це Vector довжини 3. Компілятор може використати цю інформацію для виявлення помилок.

Використовуємо метод vAdd для попарного складання значень :

val l1 = 1 :#: 2 :#: 3 :#: VNilval l2 = 1 :#: 2 :#: 3 :#: VNil val l3 = l1 vAdd l2 // Результат: l3 = 2 :#: 4 :#: 6 :#: VNil

Код вище працює, тому що система типів знає, що обидві структури мають однакову довжину, рівну 3. Але якщо ми спробуємо застосувати метод vAdd до структур різної довжини, помилка виявиться до виконання, тобто на етапі компіляції:

val l1 = 1 :#: 2 :#: 3 :#: VNilval l2 = 1 :#: 2 :#: VNil val l3 = l1 vAdd l2 // Результат: помилка компіляції

Бібліотека shapeless досить приваблива, але існують набагато потужніші рішення, наприклад, мова Idris, у якому типи є об’єктами першого класу, і, відповідно, система залежних типів реалізована елегантнее і чистіше. Заради інтересу можете подивитися порівняльну презентацію Scala vs Idris: Dependent Types, Now and in the Future.

Різні методи формальної верифікації розробляються досить давно, але часто виявляються занадто громіздкими для використання в стандартному програмуванні. Система залежних типів в мовах, подібних Idris, може виявитися корисною для пошуку простіших і практичних рішень, які при цьому сильно поліпшать реалізацію концепції “система типів для пошуку помилок”. Незважаючи на певні обмеження, при правильному підході це може стати хорошим інструментом для статичних перевірок.

Конкатенативні мови програмування

Приклади мов: Forth, Kitten, Joy, Cat.

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

Звучить досить абстрактно, давайте розберемо на конкретних прикладах з мови Cat (він вважається архівним, але це ніяк не впливає на його здатність ілюструвати загальну ідею розглянутого сімейства мов):

2 3 +

Тут ми додаємо два числа в стек і викликаємо функцію +, яка бере обидва числа зі стека і робить операцію складання, а після додає результат в стек: програма виведе число 5.

Подивимося на приклад поцікавіше, що реалізовує умовну конструкцію:

def foo { 10 < [ 0 ] [ 42 ] if}20foo

Що тут відбувається? По рядках:

  1. Ми оголошуємо функцію foo. Зверніть увагу, що у функцій в Cat не вказуються параметри – усі вони прочитуються із стека.
  2. foo викликає функцію <, яка бере перше число зі стека, порівнює його з числом 10 і додає в стек результат – True чи False.
  3. Додаємо в стек значення 0.
  4. …та значення 42. Дужки потрібні для ізоляції чисел: вони використовуватимуться як гілки “then” і “else” відповідно.
  5. Функція if бере три елементи зі стека: булевий тип і дві гілки умовної конструкції. Залежно від булевого значення, вона додасть в стек число, записане в одній з гілок.
  6. Додаємо в стек число 20.
  7. Викликаємо foo.

Програма виведе число 42. Детальніше і розширене пояснення можна подивитися в статті The Joy of Concatenative Languages.

Ще ми в квітні знайшли на GitHub стековий калькулятор Clac, з ним теж варто ознайомитися, якщо зацікавила тема. Працює він ось так:

У такого стилю програмування є цікаві якості:

  • програми можна “ділити” і “склеювати” незліченою кількістю способів для створення нових програм;
  • примітний мінімалізм синтаксису, що забезпечує лаконічність коду;
  • сильно розвинена підтримка метапрограмування.

В цілому підхід украй своєрідний і примушує поламати голову.

Детальніше розібратися з принципами роботи стека можна за допомогою наших статей: “Алгоритми і структури даних для початківців: стеки і черги” і “Основні принципи програмування: стек і купа”.

Декларативне програмування

Приклади мов: Prolog, SQL.

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

Припустимо, якщо ми з нуля пишемо алгоритм для сортування злиттям на мові C, нам треба покроково описати, як рекурсивно розділити набір даних навпіл і з’єднати його знову у впорядкованому виді (приклад коду).

Якби ми сортували числа на декларативній мові типу Prolog, замість цього ми описали б те, що хочемо отримати на виході: “Я хочу список з тих же значень, але кожен елемент з індексом i має бути менший або дорівнює елементу з індексом i+1“. Порівняйте приведений вище код на C з кодом на Prolog:

sort_list (Input, Output)  :- permutation (Input, Output), check_order (Output). check_order ([]).check_order ([Head]).
check_order ([First, Second | Tail])  :- First =< Second, check_order ([Second | Tail]).

Якщо ви коли-небудь працювали з SQL, то ви, можливо не усвідомлюючи цього, застосовували декларативний підхід: при формуванні запиту типу вибрати X з Y де Z ви описуєте набір даних, який хочете отримати. При цьому движок бази даних сам розбирається, як виконати запит. У більшості БД можна використати команду EXPLAIN для того, щоб подивитися план виконання і зрозуміти його деталі.

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

sudoku (Puzzle, Solution)  :- Solution = Puzzle, Puzzle =[S11, S12, S13, S14, S21, S22, S23, S24, S31, S32, S33, S34, S41, S42, S43, S44], fd_domain (Solution, 1, 4), Row1 =[S11, S12, S13, S14], Row2 =[S21, S22, S23, S24], Row3 =[S31, S32, S33, S34], Row4 =[S41, S42, S43, S44], Col1 =[S11, S21, S31, S41], Col2 =[S12, S22, S32, S42], Col3 =[S13, S23, S33, S43], Col4 =[S14, S24, S34, S44], Square1 =[S11, S12, S21, S22], Square2 =[S13, S14, S23, S24], Square3 =[S31, S32, S41, S42], Square4 =[S33, S34, S43, S44], valid ([Row1, Row2, Row3, Row4, Col1, Col2, Col3, Col4, Square1, Square2, Square3, Square4]). valid ([]).valid ([Head | Tail])  :- fd_all_different (Head), valid (Tail).

Так ми запускаємо вирішувач, описаний вище :

| ?- sudoku ([_, _, 2, 3, _, _, _, _, _, _, _, _, 3, 4, _, _], Solution). S =[4,1,2,3,2,3,4,1,1,2,3,4,3,4,1,2]

Сумний недолік декларативного підходу полягає в тому, що часто доводиться стикатися з проблемами продуктивності. Алгоритм вищий, швидше за все, O (n!), а наш вирішувач судоку використовує брутфорс при пошуку. І взагалі, у більшості випадків при складанні запитів до БД доводиться застосовувати додаткові підказки і покажчики для того, щоб уникнути дорогих і неефективних планів виконання.

Візуальне програмування

Приклади мов: Pure Data, Grasshopper 3d.

Візуальні мови програмування (VPL) дозволяють використати в розробці графічні елементи замість текстових команд. Такі елементи, будучи систематично впорядкованими і такими, що становлять основний контекст мови, грають роль введення і/або виведення, процесів і зв’язків усередині програми.

Зараз одним з популярних підходів є інтуїтивно природне поєднання візуального програмування з програмуванням потоків даних, при якому програма моделюється у вигляді орієнтованого графа потоку даних між операціями.

Наприклад, мова Pure Data відноситься до обох цих парадигм. Вона була розроблена в 1990-і роки для створення інтерактивних комп’ютерних музичних і мультимедійних творів.

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

Одним з таких інструментів, представлених у відповідному огляді, являється Modkit – середовище розробки під платформу Arduino:

Ще один яскравий приклад – це Blueprints Visual Scripting для Unreal Engine, скриптова мова, що дозволяє дизайнерам проектувати ігри на рівні, зазвичай доступному тільки програмістам.

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

Наукомістке програмування

Приклади мов: Wolfram Language.

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

Езотеричні мови програмування

Приклади мов: Brainfuck, FALSE.

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

Щоб зрозуміти, наскільки езотеричні мови частенько відрізняються від класичних, досить подивитися на декілька прикладів програм Hello, World!:

  • Brainfuck – одна з найвідоміших мов програмування такого роду, що спровокував створення цілої групи інших мов. За деяким винятком, усі символи окрім ><+-.,[] вважаються за коментарі і ігноруються:
++++++++++[>+++++++>++++++++++>+++>+<<<<-]>++.>+.+++++++.+++.>++.<<+++++++++++++++.>.+++.------.--------.>+.>.
  • Brainloller – клон Brainfuck, в якому команди прочитуються з пікселів зображення у форматі,PNG:

  • Pi – грунтований на Brainfuck. Він обчислює знаки числа π, при цьому періодично здійснюючи помилки і таким чином кодуючи заплутані Brainfuck- інструкції:
3.1415926535897932384226433232725028841271693992751358239749245923072164062 82208998678034805343112062982148386533222336647090844
  • Beatnik – стекова мова, код якої нагадує пропозиції англійською мовою, що часто не несуть ніякого смислового навантаження. “Вартість” слів визначається правилами гри Scrabble і використовується для ухвалення рішення про те, яку операцію необхідно виконати:
Soars, larkspurs, rains.Indistinctness.Mario snarl (nurses, natures, rules...) sensuously retries goal.Agribusinesses' costs par lain ropes (mopes) autos' cores.Tuner ambitiousness.Flit.Dour entombment.Legals' saner kinking lapse.Nests glint.
Dread, tied futures, dourer usual tumor grunts alter atonal garb tries shouldered coins.Taste a vast lustiness.Stile stuns gad subgroup gram lanes.Draftee insurer road: cuckold blunt, strut sunnier.Rely enure pantheism: arty gain groups (genies, pan) titters, tattles, nears.Bluffer tapes? Idle diatom stooge!Feted antes anklets ague? Remit goiter gout!Doubtless teared toed alohas will dull gangs' aerials' tails' sluices;Gusset ends! Gawkier halo!Enter abstruse rested loser beer guy louts.Curtain roams lasso weir lupus stunt.Truant bears animate talon. Entire torte originally timer.Redo stilt gobs.Utter centaurs;Urgent stars;Usurers (dilute);Noses;Bones;Brig sonar graders;Utensil silts;Lazies.Fret arson veterinary rows.Atlas grunted: "Pates, slues, sulfuric manor liaising tines, trailers, rep... unfair! Instant snots"!Sled rested until eatery fail.Ergs fortitude Indent spotter
Euros enter egg.Curious tenures.Torus cutlasses.Sarong torso earns cruel lags it reeled.Engineer: "Erase handbag -- unite ratification"!oaring oaten donkeys unsold, surer rapid saltest tagsBUTTERED TIBIA LUGS REWIRING TOILETSanion festers raring edit epilogues.DIRGE ROTOR.linnet oaring.GORE BOOTIES.Ironed goon lists tallest sublets --Riots, Raucous onset.Ignobly, runners' diet anguishes sunrise loner.Erode mob, slier switcher!Loaners stilt drudge pearl atoll, risking hats' ends.Rebind sitters.Toga epistles -- crud lard. (Pager purse dons souls.)glob title a curio hired rites shed suds lade grease strut arctic revs toadunless idlers rind stilt region land GERMICIDES SULTANA GUTS gill siting leansnice spurstests glovesroused aspHoles! Moles! (Sores!)Hygienists! Scars! (Asses!)Smells spell rares.Cubs instant sing in parse goodies.Rosin. Unhelpful sisal acres. Slope told.
MALENESS PASTA LAB. "Infirmary vine," rang illiterates (beans).Rosin sours, insults truss abalones, nailed rules, helical atlases.Dear remodeling stings mar rents.Sunless shiner orb (silly idol.) Clarity disses senna.Vagabonds sauted; sloes performed gelds.Alter post radial lip sectioning gums.Saint Towellings.Larger aeons telephone stolid char, pal!Boats Dean forsook, rosters, tunas, terrariums -- united, traced.Nude pagoda careens.

Так-так, це зараз був “Hello, World”!

Залишити відповідь

Ваша e-mail адреса не оприлюднюватиметься. Обов’язкові поля позначені *