Яку проблему вирішують алгебраїчні типи даних?


18

Справедливе попередження, я новачок у функціональному програмуванні, тому я можу дотримуватися багатьох поганих припущень.

Я дізнався про алгебраїчні типи. Багато функціональних мов, здається, мають їх, і вони досить корисні в поєднанні зі збігом шаблонів. Однак яку проблему вони насправді вирішують? Я можу реалізувати начебто (на зразок) алгебраїчний тип у C #, як це:

public abstract class Option { }
public class None : Option { }
public class Some<T> : Option
{
    public T Value { get; set; }
}

var result = GetSomeValue();
if(result is None)
{
}
else
{
}

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


6
Функціональне програмування - це інша парадигма, ніж об'єктно-орієнтоване програмування.
Базиль Старинкевич

@BasileStarynkevitch я розумію, але є такі мови, як F #, які трохи обох. Питання не стільки в функціональних мовах, скільки в тому, які проблеми вирішують алгебраїчні типи даних.
ConditionRacer

7
Що станеться, коли я визначу class ThirdOption : Option{}і дам тобі, new ThirdOption()де ти очікував, Someабо None?
амон

1
@amon мови, які мають типи сум, зазвичай мають способи заборонити це. Наприклад, Haskell визначає data Maybe a = Just a | Nothing(еквівалентний data Option a = Some a | Noneу вашому прикладі): ви не можете додавати третій випадок після закінчення. У той час як ви можете сортувати емулювати типи сум у C # так, як ви показали, це не найкрасивіше.
Martijn

1
Я думаю, що це менше "яку проблему вирішують рекламні агенти" і більше схожа на "ADT - це інший спосіб наближення до речей".
Математична

Відповіді:


44

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

Алгебраїчні типи даних є подвійними до цього, вони закриті . Всі випадки даних перераховані в одному місці, і операції не тільки можуть перераховувати варіанти вичерпно, їх рекомендують робити. Отже, написання нової функції, яка працює на алгебраїчному типі даних, є тривіальною: Просто запишіть прокляту функцію. Натомість додавання нових справ є складним, оскільки вам потрібно переглядати всю базу коду та розширювати кожен match. Аналогічно ситуації з інтерфейсами, як у стандартній бібліотеці Руста, додавання нового варіанту - це переломна зміна (для публічних типів).

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


Чи виникає помилка компілятора, якщо ви не вдається оновити збіг, або це з'ясується лише в час виконання?
Ян

6
@Ian Більшість функціональних мов набираються статично і перевіряють вичерпність відповідності шаблонів. Однак, якщо є шаблон "catch all", компілятор радий, навіть якщо функції доведеться мати справу з новим випадком, щоб виконати свою роботу. Крім того, вам потрібно перекомпілювати весь залежний код, ви не можете скласти лише одну бібліотеку та повторно пов’язати її з уже вбудованою програмою.

Також статична перевірка того, що модель є вичерпною, є загалом дорогою. Він вирішує проблему SAT в кращому випадку, а в гіршому - мова дозволяє отримати довільні предикати, що робить це нерозбірливим.
usr

@usr Немає мови Я знаю про спроби ідеального рішення, або компілятор розуміє, що це вичерпно, або ви змушені додати випадок, коли ви забиваєтесь і записуєте. Я не знаю, що стосується SAT, чи є у вас посилання на зменшення? Незважаючи на те, що фактичний код, написаний у реальних програмах, перевірка вичерпності - це падіння у відро.

Уявіть, що ви збігаєтесь на N буленів. Потім ви додаєте пропозиції відповідності типу (a, b, _, d, ...). Випадок викриття - це! Clause1 &&! Clause2 && .... Це мені схоже на SAT.
usr

12

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

Можливо, це спрощення, але так.

Що ще мені не вистачає?

Давайте будемо зрозуміти, що таке алгебраїчні типи даних (підсумовуючи це прекрасне посилання з сайту Learn you as Haskell):

  • Тип суми, який говорить "це значення може бути A або B".
  • Тип продукту, який говорить "це значення є і A, і B".

ваш приклад справді працює лише з першим.

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

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

Яку проблему вирішують алгебраїчні типи даних?

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


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

@mehrdad - Я думаю, що це трохи завищить.
Теластин

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

6

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

Переважно те, чого вам не вистачає, є цілий ряд функцій, створених для полегшення роботи з параметрами. Розглянемо основний приклад із документів Scala:

val name: Option[String] = request getParameter "name"
val upper = name map { _.trim } filter { _.length != 0 } map { _.toUpperCase }
println(upper getOrElse "")

Зверніть увагу на те, як mapі filterдозволити здійснювати ланцюжок операцій на "Параметри", не перевіряючи, чи є у вас Noneчи ні. Потім в кінці ви використовуєте getOrElseдля визначення значення за замовчуванням. Ні в якому разі ви не робите щось "грубе", як-от перевірка типів. Будь-яка неминуча перевірка типу проводиться всередині бібліотеки. У Haskell є власний набір аналогових функцій, не кажучи вже про великий набір функцій, який буде працювати на будь-яку монаду чи функтор.

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.