Сувора позитивність


10

З цієї посилання: Сувора позитивність

Умова суворої позитивності виключає такі декларації, як

data Bad : Set where
 bad : (Bad → Bad) → Bad
         A      B       C
 -- A is in a negative position, B and C are OK

Чому A негативний? Також чому B дозволено? Я розумію, чому це дозволено.


1
Я не впевнений, чому це називається "негативним", але це більше відоме помилка, яку він створює: переповнення стека :) Цей код може спричинити нескінченне розширення Aі вибухнути стек зрештою (на мовах на основі стека).
wvxvw

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

1
Я думаю, що було б добре сказати про неприпинення в тілі свого питання. Я оновив свою відповідь, грунтуючись на вашому коментарі.
Антон Трунов

@wvxvw Не обов'язково, він може просто працювати назавжди без вибуху стека, якщо компілятор реалізує хвостову рекурсію, наприклад, мій приклад в OCaml нижче не вибухає стек.
Антон Трунов

1
@AntonTrunov впевнений, що це скоріше каламбур на ім’я сайту, а не спроба бути точним.
wvxvw

Відповіді:


17

Спочатку термінологічне пояснення: негативні та позитивні позиції походять від логіки. Вони про асиметрію в логічних зв'язок: в поводиться по- різному від . Подібне відбувається і в теорії категорій, де ми говоримо противаріантно і ковариантно замість негативного та позитивного відповідно. У фізиці вони говорять про величини, які поводяться "коваріантно" і "противажно". Тому це дуже загальне явище. Програміст може вважати їх "входом" і "виходом".A BABAB

Тепер на індуктивні типи даних.

Подумайте індуктивний тип даних в якості свого роду алгебраїчної структури: конструктори операції , які приймають елементи в якості аргументів і виробляють нові елементи . Це дуже схоже на звичайну алгебру: додавання займає два числа і утворює число.T TTTT

В алгебрі прийнято, що операція приймає обмежену кількість аргументів, і в більшості випадків вона бере нульовий (постійний), один (унарний) або два (двійкові) аргументи. Це зручно узагальнити для конструкторів типів даних. Припустимо c, це конструктор для типу даних T:

  • якщо cце константа, ми можемо вважати це функцією unit -> T, або рівнозначно (empty -> T) -> T,
  • якщо cє одинарним, ми можемо вважати це функцією T -> Tабо рівнозначно (unit -> T) -> T,
  • якщо cє двійковим, ми можемо вважати це функцією T -> T -> T, або рівнозначно T * T -> T, або рівнозначно (bool -> T) -> T,
  • якби ми хотіли конструктор, cякий бере сім аргументів, ми могли б розглянути його як функцію, (seven -> T) -> Tде sevenє певний раніше визначений тип із семи елементами.
  • ми також можемо мати конструктор, cякий бере незліченно безліч аргументів, це було б функцією (nat -> T) -> T.

Ці приклади показують, що загальна форма конструктора повинна бути

c : (A -> T) -> T

де ми називаємо Aна арность про cі ми думаємо, cяк конструктор , який приймає A-many аргументів типу Tдля отримання елемента T.

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

broken: (T -> T) -> T

тоді питання "скільки аргументів brokenбере?" не має гарної відповіді. Ви можете спробувати відповісти на це "знадобиться Tбагато аргументів", але це не буде, оскільки Tце ще не визначено. Ми можемо спробувати вийти з кундуру, використовуючи фантастичну теорію з фіксованою точкою, щоб знайти тип Tта ін'єкційну функцію (T -> T) -> T, і це вдалося б досягти успіху, але ми також порушили принцип індукції T. Отже, просто погано спробувати спробувати таке.

Для повноти дозвольте пояснити всю історію. Нам потрібно трохи узагальнити вищевказану форму конструкторів. Іноді ми маємо операції або конструктори, які приймають параметри . Наприклад, скалярне множення займає скалярне і вектор для отримання вектора . Це одинарна операція на векторах, параметризована скаляром. Ми можемо розглянути скалярне множення як нескінченно багато одинарних операцій, по одній для кожного скаляра, але це дратує. Отже, загальна форма конструктора повинна допускати параметри якогось типу :v λ vλvλvcB

c : B * (A -> T) -> T

Дійсно, багато конструктори можна переписати таким чином, але не всі, нам потрібно ще один крок, а саме ми повинні дозволити , Aщоб залежати від B:

c : (∑ (x : B), A x -> T) -> T

Це остаточна форма конструктора для індуктивного типу. Це також саме W-типи. Форма настільки загальна, що нам потрібен лише один конструктор c! Дійсно, якщо у нас їх два

d' : (∑ (x : B'), A' x -> T) -> T
d'' : (∑ (x : B''), A'' x -> T) -> T

то ми можемо об'єднати їх в одне ціле

d : (∑ (x : B), A x -> T) -> T

де

B := B' + B''
A(inl x) := A' x
A(inr x) := A'' x

До речі, якщо ми викриваємо загальну форму, ми бачимо, що вона рівнозначна

c : ∏ (x : B), ((A x -> T) -> T)

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


1
Ще раз дякую Андрію після мого обіду, це для мене буде найважче перетравити. Ура.
Пушпа

9

Перше виникнення Badназивається "негативним", оскільки воно представляє аргумент функції, тобто знаходиться ліворуч від стрілки функції (див. Рекурсивні типи безкоштовно від Філіпа Вадлера). Я думаю, походження терміна "негативна позиція" походить від поняття протиріччя ("проти" означає протилежне).

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

Ми допускаємо друге виникнення, Badоскільки це не викликає припинення, і ми хочемо використовувати тип, який визначається ( Bad), в якийсь момент рекурсивного типу даних ( перед останньою стрілкою його конструктора).

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

data Good : Set where
  good : Good → Good → Good

Правило поширюється лише на аргументи конструктора (які є і Goodв цьому випадку), і не на сам конструктор (див. Також " Сертифіковане програмування з залежними типами " Адама Чліпала ).

Ще один приклад порушення суворої позитивності:

data Strange : Set where
  strange : ((Bool → Strange) → (ℕ → Strange)) → Strange
                       ^^     ^
            this Strange is   ...this arrow
            to the left of... 

Ви також можете перевірити цю відповідь про негативні позиції.


Детальніше про неприпинення ... Сторінка, на яку ви посилаєтесь, містить деякі пояснення (разом із прикладом у Haskell):

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

Ось аналогічний приклад у Ocaml, який показує, як реалізувати рекурсивну поведінку без (!) За допомогою рекурсії безпосередньо:

type boxed_fun =
  | Box of (boxed_fun -> boxed_fun)

(* (!) in Ocaml the 'let' construct does not permit recursion;
   one have to use the 'let rec' construct to bring 
   the name of the function under definition into scope
*)
let nonTerminating (bf:boxed_fun) : boxed_fun =
  match bf with
    Box f -> f bf

let loop = nonTerminating (Box nonTerminating)

У nonTerminatingфункції «розпаковується» функція від аргументу і яблук з оригіналом аргументу. Тут важливо те, що більшість типів систем не дозволяють передавати функції самому собі, тому такий термін, як f fне буде набір перевірки, оскільки не існує типу, який fби задовольняв контролеру типу. Однією з причин впровадження типів систем є відключення самозастосування (див. Тут ).

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

Хочу додати, що невпинні обчислення вносять невідповідність логічним системам. У випадку Agda та Coq Falseіндуктивний тип даних не має конструкторів, тому ви ніколи не зможете побудувати термін підтвердження типу False. Але якщо дозволені незакінчені обчислення, можна зробити це, наприклад, таким чином (у Coq):

Fixpoint loop (n : nat) : False = loop n

Тоді loop 0слід набрати перевірку loop 0 : False, тому під листуванням Кері -Говарда це означає, що ми виявили помилкове твердження.

Підсумок : суворе правило позитивності для індуктивних визначень запобігає незакінченню обчислень, згубних для логіки.


Тепер я розгублений. Спеціальні дані Добре: Встановіть, де добре: Добре → Добре →. Ми спробуємо розібратися і повернемось через годину /
Пушпа

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