Чому функція з поліморфним типом `forall t: Type, t-> t` має бути тотожною функцією?


18

Я новачок у теорії мови програмування. Я дивився деякі онлайн-лекції, в яких викладач стверджував, що функція з поліморфним типом forall t: Type, t->tє тотожністю, але не пояснив, чому. Може хтось мені пояснить, чому? Може бути доказом претензії з перших принципів.


3
Я вважав, що це питання має бути дублікатом, але не можу його знайти. cs.stackexchange.com/questions/341/… - це своєрідне спостереження. Стандартне посилання - Теореми безкоштовно! Філ Вадлер.
Жил "ТАК - перестань бути злим"

1
Спробуйте побудувати загальну функцію з цим типом, яка робить все інше. Ви виявите, що його немає.
Бергі

@Bergi Так, я не зміг знайти жодного зустрічного прикладу, але все ще не знав, як це довести.
абхишек

Але які були ваші спостереження, коли ви намагалися їх знайти? Чому ваші спроби не спрацювали?
Бергі

@Gilles Можливо, ви пам’ятаєте cs.stackexchange.com/q/19430/14663 ?
Бергі

Відповіді:


32

Перше, що слід зазначити, це не обов'язково правда. Наприклад, залежно від мови, функція цього типу, окрім функції тотожності, могла: 1) циклічно вічно, 2) мутувати деякий стан, 3) повернутись null, 4) кинути виняток, 5) виконати деякий введення-виведення, 6) розкладіть потік, щоб зробити щось інше, 7) зробіть call/ccшенанігани, 8) використовуйте щось на зразок Java Object.hashCode, 9) використовуйте відображення, щоб визначити, чи є ціле число, і збільшуйте його, якщо так, 10) використовуйте відображення для аналізу стека викликів і зробити щось, виходячи з контексту, в якому він називається, 11) ймовірно, багато інших речей і, безумовно, довільні комбінації викладеного.

Тож властивість, що призводить до цього, параметричність, є властивістю мови в цілому і є більш сильні та слабкі її варіації. Для багатьох формальних розрахунків, що вивчаються в теорії типів, жодне з перерахованих вище поведінки не може відбутися. Наприклад, для Системи F / чистого поліморфного обчислення лямбда, де параметричність вперше була вивчена, жодне з перерахованих вище способів поведінки не може відбутися. Він просто не має винятків, змінюване стан, null, call/cc, I / O, відображення, і це сильно нормалізації , тому він не може бути зациклений назавжди. Як згадував Жил у коментарі, стаття Теореми безкоштовно!Філ Вейдлер - це хороший вступ до цієї теми, і її посилання заглиблюватимуться далі в теорію, зокрема техніку логічних відносин. Це посилання також перераховує деякі інші роботи Вадлера на тему параметричності.

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


1
Як система F уникала нескінченних циклів, які система типу не може виявити? Це класифікується як нерозв'язне в загальному випадку.
Джошуа

2
@Joshua - стандартний доказ неможливості для проблеми зупинки починається з припущення, що в першу чергу існує нескінченна петля. Отож, викликати це питання, чому система F не має нескінченних циклів - це кругові міркування. Загалом, Система F майже не завершена Тьюрінгом, тому я сумніваюся, що вона задовольняє будь-якому з припущень цього доказу. Досить легко слабкий комп'ютер, щоб довести, що всі його програми припиняються (немає рекурсії, немає циклів під час, лише дуже слабкі для циклів тощо).
Джонатан У ролях

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

15

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

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


8

З усіма застереженнями, про які згадує Дерек, і ігноруючи парадокси, що є результатом використання теорії множин, дозвольте мені навести ескіз доказу в дусі Рейнольдса / Вадлера.

Функція типу:

f :: forall t . t -> t

- це сімейство функцій індексується типом tftt .

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

fg

forall t . t -> t

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

fstfsft

()()t()t((), c)ctf()ftf()()()ftcc()cftidttfid

Більше деталей ви можете знайти в моєму блозі .


-2

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

Помилковий!

function f(a): forall t: Type, t->t
    function g(a): forall t: Type, t->t
       return (a is g) ? f : a
    return a is f ? g : a

де isоператор порівнює дві змінні для еталонної ідентичності. Тобто вони містять однакове значення. Не еквівалентне значення, однакове значення. Функції fта gеквівалентні за деяким визначенням, але вони не однакові.

Якщо ця функція передана сама, вона повертає щось інше; інакше він повертає свій вклад. Щось інше має той самий тип, що і саме його, тому його можна замінити. Іншими словами, fце не тотожність, тому що f(f)повертається g, тоді як ідентичність повертається f.

Щоб теорема була дотримана, вона повинна припускати смішну здатність до скорочення

function cantor(n, <z, a>) : forall t: t: Type int, <int, t> -> <int, t>
    return n > 1 ? cantor((n % 2 > 0) ? (n + 1) : n / 2, <z + 1, a>) : <z, a>
return cantor(1000, <0, a>)[1]¹

Якщо ви готові припустити, що ви можете припустити, що набагато простіше можна зробити висновок про тип.

Якщо ми спробуємо обмежити область до тих пір, поки теорема не витримає, ми зрештою повинні обмежити її жахливо.

  • Чистий функціонал (без змінного стану, без IO). Гаразд, я можу з цим жити. Багато часу ми хочемо виконувати докази над функціями.
  • Порожня стандартна бібліотека. мех.
  • Ні raiseі ні exit. Тепер ми починаємо обмежуватися.
  • Нижнього типу немає.
  • У мові є правило, яке дозволяє компілятору згортати нескінченну рекурсію, вважаючи, що вона повинна припинитися. Компілятору дозволено відкидати тривіальну нескінченну рекурсію.
  • Компілятор може відмовити, якщо представлений щось, що не може бути доведено жодним чином .² Тепер стандартна бібліотека не може приймати функції як аргументи. Бу
  • Немає nil. Це починає ставати проблематичним. У нас не вистачає способів боротьби з 1/100
  • Мова не може робити умовиводи філіальної форми та не може переосмислити те, коли програміст може довести висновок типу, що не може бути. Це досить погано.

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

¹ Якщо ви думаєте, що компілятор може вивести цей, спробуйте цей

function fermat(z) : int -> int
    function pow(x, p)
        return p = 0 ? 1 : x * pow(x, p - 1)
    function f2(x, y, z) : int, int, int -> <int, int>
        left = pow(x, 5) + pow(y, 5)
        right = pow(z, 5)
        return left = right
            ? <x, y>
            : pow(x, 5) < right
                ? f2(x + 1, y, z)
                : pow(y, 5) < right
                    ? f2(2, y + 1, z)
                    : f2(2, 2, z + 1)
    return f2(2, 2, z)
function cantor(n, <z, a>) : forall t: t: Type int, <int, t> -> <int, t>
    return n > 1 ? cantor((n % 2 > 0) ? (n + 1) : n / 2, <z + 1, a>) : <z, a>
return cantor(fermat(3)[0], <0, a>)[1]

² Доказ того, що компілятор не може цього зробити, залежить від осліплення. Ми можемо використовувати кілька бібліотек, щоб переконатися, що компілятор не може побачити цикл відразу. Крім того, ми завжди можемо щось побудувати там, де програма працюватиме, але не може бути складена, оскільки компілятор не може виконати індукцію у доступній пам'яті.

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

function f(a, b, c): t: Type: t[],int,int->t
    return a[b/c]

не повинен компілювати. Основна проблема полягає в тому, що індексація масиву виконання не працює більше.


@ Бергі: Я сконструював контрприклад.
Джошуа

1
Будь ласка, знайдіть хвилину, щоб поміркувати над різницею між вашою відповіддю та іншими двома. Вступне слово Дерека - "Перше, що слід зазначити, це не обов'язково правда". А потім він пояснює, які властивості мови роблять це правдою. jmite також пояснює, що робить це правдою. На противагу цьому, ваша відповідь наводить приклад невизначеною (і нечастою мовою) з нульовим поясненням. (Що таке foilкількісний показник?) Це зовсім не корисно.
Жил "ТАК - перестань бути злим"

1
@DW: якщо a f, то тип a - це тип f, який також є типом g, і тому слід перевірити тип перевірки. Якщо справжній компілятор вигнав це, я б застосував формат виконання, який справжні мови завжди мають, щоб система статичного типу помилилася, і вона ніколи не вийде з ладу під час виконання.
Джошуа

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

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