Процедурне / функціональне програмування нічим не слабше, ніж OOP , навіть не вдаючись до аргументів Тьюрінга (моя мова має силу Тюрінга і може робити все, що зробить інший), що не означає багато. Власне, об'єктно-орієнтовані методи вперше експериментували на мовах, які не мали їх вбудованої. У цьому сенсі програмування ОО - це лише специфічний стиль процедурного програмування . Але це допомагає виконувати певні дисципліни, такі як модульність, абстрагування та приховування інформації, які мають важливе значення для зрозумілості та підтримки програми.
Деякі парадигми програмування розвиваються з теоретичного бачення обчислення. Мова на зразок Ліспа розвинулася з лямбда-числення та ідеї метациркуляції мов (подібно до рефлексивності в природній мові). Запропоновані ріжками пропонують програмування Prolog та обмеження. Сімейство Алгол також завдячує лямбда-числення, але без вбудованої рефлексивності.
Лісп - цікавий приклад, оскільки він був зразком багатьох інновацій мови програмування, що простежується до його подвійної генетичної спадщини.
Однак мови тоді розвиваються, часто під новими назвами. Основним фактором еволюції є практика програмування. Користувачі визначають практику програмування, що покращує властивості програм, такі як читабельність, ремонтопридатність, доказовість правильності. Потім вони намагаються додати до мов функції або обмеження, які підтримуватимуть, а іноді і застосовувати ці практики, щоб покращити якість програм.
Це означає, що ці практики вже можливі в більш старій мові програмування, але для їх використання потрібно розуміння та дисциплінованість. Включення їх у нові мови як основні поняття із специфічним синтаксисом полегшує використання цих практик та їх легкого розуміння, особливо для менш складних користувачів (тобто переважна більшість). Це також полегшує життя досвідченим користувачам.
Якимось чином, це мовне проектування, що означає підпрограма / функція / процедура для програми. Після виявлення корисної концепції їй надають назву (можливо) та синтаксис, щоб її можна було легко використовувати у всіх програмах, розроблених цією мовою. І коли це буде успішно, воно буде включено і в майбутні мови.
Приклад: відтворення орієнтації об'єкта
Я зараз намагаюся проілюструвати це на прикладі (який, безумовно, можна було б додатково відшліфувати, враховуючи час). Мета прикладу - не показати, що об’єктно-орієнтована програма може бути написана в процедурному стилі програмування, можливо, за рахунок доступності та ремонтопридатності. Я скоріше спробую показати, що деякі мови без засобів OO можуть фактично використовувати функції вищого порядку та структуру даних, щоб фактично створити засоби для ефективного імітації Орієнтації на об'єкти , щоб отримати користь від його якостей щодо організації програми, включаючи модульність, абстрагування та приховування інформації .
Як я вже говорив, Лісп був зразком еволюції мови, включаючи парадигму ОО (хоча першою мовою ОО можна вважати Симула 67, в родині Алгол). Lisp дуже простий, а код для його основного перекладача - менше сторінки. Але ви можете зайнятися програмуванням OO в Lisp. Все, що вам потрібно, - це функції вищого порядку.
Я не буду використовувати езотеричний синтаксис Lisp, а скоріше псевдокод, щоб спростити презентацію. І я розгляну просту істотну проблему: приховування інформації та модульність . Визначення класу об'єктів, не дозволяючи користувачеві отримувати доступ (більшу частину) реалізації.
Припустимо, я хочу створити клас під назвою вектор, що представляє двовимірні вектори, методами, що включають: додавання вектора, розмір вектора та паралелізм.
function vectorrec () {
function createrec(x,y) { return [x,y] }
function xcoordrec(v) { return v[0] }
function ycoordrec(v) { return v[1] }
function plusrec (u,v) { return [u[0]+v[0], u[1]+v[1]] }
function sizerec(v) { return sqrt(v[0]*v[0]+v[1]*v[1]) }
function parallelrec(u,v) { return u[0]*v[1]==u[1]*v[0]] }
return [createrec, xcoordrec, ycoordrec, plusrec, sizerec, parallelrec]
}
Тоді я можу призначити створений вектор фактичним іменам функцій, які будуть використовуватися.
[вектор, xcoord, ycoord, vplus, vsize, vparallel] = vectorclass ()
Чому бути таким складним? Оскільки я можу визначити у функції vectorrec посередницькі конструкції, які я не хочу бути видимими для решти програми, щоб зберегти модульність.
Ми можемо зробити ще одну колекцію за полярними координатами
function vectorpol () {
...
function pluspol (u,v) { ... }
function sizepol (v) { return v[0] }
...
return [createpol, xcoordpol, ycoordpol, pluspol, sizepol, parallelpol]
}
Але я, можливо, хочу байдуже використовувати обидві реалізації. Один із способів зробити це - додати компонент типу до всіх значень і визначити всі вищевказані функції в одному середовищі: Потім я можу визначити кожну з повернутих функцій, щоб вона спочатку перевірила тип координат, а потім застосувала конкретну функцію для нього.
function vector () {
...
function plusrec (u,v) { ... }
...
function pluspol (u,v) { ... }
...
function plus (u,v) { if u[2]='rec' and v[2]='rec'
then return plusrec (u,v) ... }
return [ ..., plus, ...]
}
Що я здобув: конкретні функції залишаються невидимими (через локалізацію локальних ідентифікаторів), а решта програми може використовувати лише найбільш абстрактні, повернені дзвінками до векторного класу.
Одне заперечення полягає в тому, що я міг би безпосередньо визначити кожну з абстрактних функцій програми і залишити всередині визначення функцій, що залежать від координат. Тоді також буде приховано. Це правда, але тоді код для кожного типу координат буде розрізаний невеликими шматочками, розповсюдженими по програмі, що є менш доступним для редагування та ремонтування.
Насправді мені навіть не потрібно давати їм ім’я, і я можу просто зберегти як анонімні функціональні значення в структурі даних, індексовану типом і рядком, що представляє ім'я функції. Ця структура, локальна для вектора функцій, була б непомітною для решти програми.
Для спрощення використання, замість повернення списку функцій, я можу повернути єдину функцію, що називається, застосувати, беручи за аргумент явно введені значення та рядок, і застосувати функцію з відповідним типом та назвою. Це дуже схоже на виклик методу для класу ОО.
Я зупинюсь тут, на цій реконструкції об’єктно-орієнтованого об’єкта.
Що я намагався зробити - це показати, що побудувати корисну орієнтацію об'єкта достатньо потужною мовою, включаючи успадкування та інші подібні особливості, не надто важко. Метациркулярність перекладача може допомогти, але здебільшого на синтаксичному рівні, який все ще далеко не незначний.
Перші користувачі об'єктної орієнтації саме так експериментували з концепціями. І це, як правило, стосується багатьох вдосконалень мов програмування. Звичайно, теоретичний аналіз також відіграє певну роль і допоміг зрозуміти або уточнити ці поняття.
Але думка про те, що мови, які не мають функцій ОО, в деяких проектах приречені на збій, просто необґрунтована. За потреби вони можуть досить імітувати реалізацію цих функцій. Багато мов мають синтаксичну та семантичну силу досить ефективно робити орієнтацію на об'єкти, навіть коли вона не вбудована. І це більше, ніж аргумент Тьюрінга.
OOP не стосується обмежень інших мов, але він підтримує або застосовує методології програмування, що допомагає писати кращу програму, тим самим допомагаючи менш досвідченим користувачам дотримуватися належних практик, якими користуються та розробляють більш просунуті програмісти без цієї підтримки.
Я вважаю, що гарною книгою для розуміння всього цього може стати Abelson & Sussman: структура та інтерпретація комп'ютерних програм .