Mathematica: що таке символічне програмування?


79

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

Мої запитання: що це за символічне програмування? І як це порівнюється з функціональними мовами (такими як Haskell)?

Відповіді:


74

Ви можете уявити символічне програмування Mathematica як систему пошуку та заміни, де ви програмуєте, вказавши правила пошуку та заміни.

Наприклад, ви можете вказати наступне правило

area := Pi*radius^2;

Наступного разу, коли ви використаєте area, його буде замінено на Pi*radius^2. Тепер, припустимо, ви визначаєте нове правило

radius:=5

Тепер, коли б ви не використовували radius, це буде переписано 5. Якщо ви оціните, areaце буде переписано на те, Pi*radius^2які тригери правило переписування для, radiusі ви отримаєте Pi*5^2як проміжний результат. Ця нова форма ініціює вбудоване правило перезапису для ^роботи, тому вираз буде переписано далі Pi*25. На цьому переписування припиняється, оскільки немає відповідних правил.

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

add[a_,b_]:=a+b

Тепер add[x,y]переписується в x+y. Якщо ви хочете, щоб додаток застосовувався лише для цифр a, b, ви можете замість цього зробити

add[a_?NumericQ, b_?NumericQ] := a + b

Тепер add[2,3]переписується на 2+3використання вашого правила, а потім на 5використання вбудованого правила для +, тоді як add[test1,test2]залишається незмінним.

Ось приклад правила інтерактивної заміни

a := ChoiceDialog["Pick one", {1, 2, 3, 4}]
a+1

Тут aвін замінюється на ChoiceDialog, який потім замінюється номером, який користувач вибрав у діалоговому вікні, яке з’явилося, що робить як величини числовими, так і правилами заміни тригерів +. Тут, ChoiceDialogяк вбудоване правило заміни, в порядку "замінити ChoiceDialog [деякі речі] значенням кнопки, яку користувач натиснув".

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

 solve[x + 5 == b_] := (result = b - 5; result /; result > 0)

Тут solve[x+5==20]він замінюється на 15, але solve[x + 5 == -20]залишається незмінним, оскільки не застосовується жодне правило. Умовою, яка перешкоджає застосуванню цього правила, є /;result>0. По суті, оцінювач розглядає потенційні результати застосування правила, щоб вирішити, чи продовжувати його.

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

myrules={area->Pi radius^2,radius->5}
area//.myrules

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

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


2
@Yaro Я припускаю, що справа стає цікавішою, коли ти отримуєш правила як результат функцій (як у Solve, DSolve тощо)
доктор Белісаріус,

Але ви можете подумати, Solveце лише черговий набір правил переписування. Коли ви даєте деякі рівняння, які Mathematica не може розв’язати, Solve[hard_equations]залишається як Solve[hard_equations]і ви можете визначити власне Solveправило, яке застосовується в цьому випадку. У цьому випадку я здогадуюсь, що вони використовують /; умовно визначати шаблон для "будь-якого рівняння, яке можна розв'язати методами в Mathematica", тому для жорстких рівнянь вбудоване правило не застосовується і Solveзалишається у вихідній формі
Ярослав Булатов,

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

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

3
Саймоне, саме лямбда-числення - це лише одна із систем переписування. Переписування термінів є більш загальним підходом, ніж будь-який конкретний TRS.
SK-logic

75

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

Mathematica активно використовує цю можливість. Навіть сильніше, ніж LISP та Prolog (IMHO).

Як приклад символічного програмування розглянемо наступну послідовність подій. У мене є файл CSV, який виглядає так:

r,1,2
g,3,4

Я прочитав цей файл у:

Import["somefile.csv"]
--> {{r,1,2},{g,3,4}}

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

Отже, тепер я застосовую перетворення до результату:

% /. {c_, x_, y_} :> {c, Disk[{x, y}]}
--> {{r,Disk[{1,2}]},{g,Disk[{3,4}]}}

Не зупиняючись на деталях, все, що сталося, - це те, що Disk[{...}]було обернуто навколо останніх двох чисел з кожного рядка введення. Результатом все одно є дані / код, але все ще інертний. Ще одне перетворення:

% /. {"r" -> Red, "g" -> Green}
--> {{Red,Disk[{1,2}]},{Green,Disk[{3,4}]}}

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

% /. x_ :> Graphics[x]
--> Graphics[{{Red,Disk[{1,2}]},{Green,Disk[{3,4}]}}]

Насправді, ви не побачите цього останнього результату. У епічному показі синтаксичного цукру Mathematica продемонструє таку картину червоних та зелених кіл:

текст заміщення

Але задоволення на цьому не зупиняється. Під усім цим синтаксичним цукром ми все ще маємо символічний вираз. Я можу застосувати інше правило трансформації:

% /. Red -> Black

текст заміщення

Престо! Червоне коло стало чорним.

Саме такий вид "натискання на символ" характеризує символічне програмування. Переважна частина програмування Mathematica має таку природу.

Функціональний проти символічний

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

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

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

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

Заключні зауваження

На закінчення я виступаю за те, що існує різниця між функціональним програмуванням (втіленим Хаскеллом) та символічним програмуванням (втіленим Mathematica). Я думаю, що якщо хтось вивчить і те, і інше, то дізнаєшся значно більше, ніж вивчення лише одного - остаточного випробування відмінності.


Дірява функціональна абстракція в Mathematica?

Так, діряво. Спробуйте це, наприклад:

f[x_] := g[Function[a, x]];
g[fn_] := Module[{h}, h[a_] := fn[a]; h[0]];
f[999]

Належним чином повідомлено WRI та визнано ним. Відповідь: уникайте використання Function[var, body]( Function[body]це нормально).


2
Чи справді WRI порадив вам уникати Function[var, body]? Це дивно, оскільки це рекомендовано в документах ...
Саймон

6
@Simon: Так, у мене є електронне повідомлення від WRI, в якому сказано, що якщо мене турбує семантика функції, що змінюється залежно від того, чи використовує якийсь абонент у "ланцюжку викликів" символ із подібним іменем, тоді я повинен уникати використання Function[var, body]. Пояснень щодо того, чому цього не вдалося виправити, не пропонувалось, але я припускаю, що з Functionтих пір, як приблизно з 1.0, було б катастрофічно змінювати свою поведінку так пізно в грі. Проблема описана в (трохи - трохи) більш детально тут .
WReach

5
З рівнем експозиції його внутрішніх елементів у mma, я навіть не впевнений, що це Functionможна вилікувати, навіть в принципі - принаймні за допомогою поточної втручаючої семантики Ruleі RuleDelayed, які не поважають прив’язок конструкцій внутрішнього обсягу, включаючи їх самих . Мені здається, це явище більше пов'язане з цією властивістю Ruleі RuleDelayed, ніж конкретно з Function. Але в будь-якому випадку, я згоден, що зараз це дуже небезпечно. Шкода, тому що Function[var,body]їх не слід використовувати - такі помилки буде практично неможливо зловити у великих проектах.
Леонід Шифрін,

@WReach Mathics , який є клоном FOSS Mathematica, не має проблемної функції! Я просто спробував.
Mohammad Alaggan

@ M.Alaggan FYI, схоже, Mathics переїхав сюди , він не пройшов перший випуск (1.0), але у нього є досить нещодавні коміти (листопад 2018 р.)
jrh,

10

Як вже згадувались інші тут, Mathematica робить багато переписування термінів. Можливо, Haskell і не найкраще порівняння, але Pure - це приємна функціональна мова для переписування термінів (яка повинна відчуватись знайомою людям з досвідом Haskell). Можливо, читання їхньої сторінки Вікі про переписування термінів зрозуміє для вас кілька речей:

http://code.google.com/p/pure-lang/wiki/Rewriting


1
Семантично Pure більше схожий на Scheme (динамічне введення тексту, за замовчуванням нетерпляче оцінюється) або OCaml (має явні посилальні комірки). Але синтаксис і бібліотеки відображають хаскелівські.
dubiousjim

6

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

Що стосується порівняння з Haskell, то ви могли б зробити там переписування за допомогою невеликої допомоги з вашої бібліотеки зразків, але це не так просто, як у динамічно набраній Mathematica.


1

Символічне не слід протиставляти функціональному, воно має протиставлятися числовому програмуванню. Розглянемо як приклад MatLab проти Mathematica. Припустимо, я хочу характеристичний поліном матриці. Якби я хотів зробити це в Mathematica, я міг би отримати матрицю ідентичності (I) і саму матрицю (A) в Mathematica, тоді зробіть це:

Det[A-lambda*I]

І я отримав би характерний багаточлен (неважливо, що, ймовірно, є характерна поліноміальна функція), з іншого боку, якби я був у MatLab, я не міг би зробити це з базовим MatLab, оскільки базовий MatLab (неважливо, що існує, мабуть, характерний поліном функція) хороша лише при обчисленні чисел кінцевої точності, а не речей, де там є випадкові лямбди (наш символ). Що вам потрібно зробити, це придбати надбудову Symbolab, а потім визначити лямбда як власний рядок коду, а потім виписати це (де це перетворило б вашу матрицю A в матрицю раціональних чисел, а не в десяткові цифри з кінцевою точністю) , і хоча різниця в продуктивності, мабуть, буде непомітною у такому невеликому випадку, це, мабуть, робить це набагато повільніше, ніж Mathematica, з точки зору відносної швидкості.

Отож різниця полягає в тому, що символічні мови зацікавлені робити обчислення з досконалою точністю (часто використовуючи раціональні числа, на відміну від числових), а мови числового програмування, з іншого боку, дуже хороші у переважній більшості обчислень, які вам потрібно буде зробити, і вони схильні щоб бути швидшими при числових операціях, для яких вони призначені (MatLab у цьому відношенні майже не має рівних для мов вищого рівня - за винятком C ++ та ін.) і невдоволення символічними операціями.

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