Могат ли функциите на Haskell да бъдат доказани/проверени чрез модел/потвърдени със свойства за коректност?

Продължавайки от идеите в: Има ли доказуеми езици от реалния свят?

Не знам за вас, но на мен писна ми да пиша код, който не мога да гарантирам.

След като зададох горния въпрос и получих феноменален отговор (Благодаря на всички!), реших да стесня търсенето си за доказуем, прагматичен подход към Haskell. Избрах Haskell, защото всъщност е полезен (има много уеб рамки, написани за него, това изглежда добър показател) И мисля, че е достатъчно строг, функционално, за да може да бъде доказуем, или поне позволяват тестване на инварианти.

Ето какво искам (и не можах да намеря)

Искам рамка, която може да разглежда функция на Haskell, добавяне, написана на псевдокод:

add(a, b):
    return a + b

- и проверете дали определени инварианти се задържат над всяко състояние на изпълнение. Бих предпочел някакво формално доказателство, но бих се задоволил с нещо като модел-проверка.
В този пример инвариантът би бил дадените стойности a и b, върнатата стойност винаги е сумата a+b.

Това е прост пример, но не мисля, че е невъзможно да съществува такава рамка. Със сигурност ще има горна граница на сложността на функция, която може да бъде тествана (10 входа на низ към функция със сигурност ще отнеме много време!), но това ще насърчи по-внимателен дизайн на функциите и не е по-различно от използването на други формални методи. Представете си, че използвате Z или B, когато дефинирате променливи/множества, вие сте сигурни, че давате на променливите възможно най-малките диапазони. Ако вашият INT никога няма да бъде над 100, уверете се, че сте го инициализирали като такъв! Техники като тези и правилната декомпозиция на проблема трябва - според мен - да позволят задоволителна проверка на чисто функционален език като Haskell.

Все още нямам много опит с формални методи или Haskell. Кажете ми дали идеята ми е добра или може би смятате, че haskell не е подходящ? Ако предложите различен език, моля, уверете се, че преминава теста „has-a-web-framework“ и прочетете оригинала въпрос :-)


person 0atman    schedule 02.11.2010    source източник
comment
Може би аз ще напиша своя?   -  person 0atman    schedule 02.11.2010
comment
просто изчакайте, докато системата ви е толкова голяма, че вече не можете да гарантирате така. вашият най-добър залог за гаранция са автоматизирани тестове и ръчно тестване.   -  person Steven    schedule 05.11.2010
comment
@Steven: Наистина ли? Защо би било така? Ако е доказано, че част от код има някакво свойство, то винаги ще има това свойство, без значение с какъв друг код взаимодейства, и можете да разчитате на това свойство в други доказателства. Доказателствата са многократно използвани и композиционни по начин, по който тестовете не са, и чрез кодиране на доказателства в системата от типове няма шанс те да излязат от синхрон със самия код.   -  person C. A. McCann    schedule 05.11.2010
comment
@Steven: Да, съгласен съм с camccann, не е нужно да тествам всяка комбинация от тухли в къща. Все пак хубава коза!   -  person 0atman    schedule 05.11.2010
comment
това е частта от код, която се интегрира с много други части от код, които също тествате. не всеки разработчик ще иска да изработи всяко малко доказателство, което имате, преди да опита да се интегрира с него.   -  person Steven    schedule 07.11.2010
comment
@Steven: Те не трябва, това е целият смисъл! Ако една функция е доказуемо правилна за всички възможни входове, тогава единственото задължение, необходимо за нейното използване, е предоставянето на аргументи от подходящ тип. Това, че не трябва да се тревожите за точките на интеграция, е точно защо кодираните с тип доказателства са композиционни. Знаейки, че част от кода е гарантирано никога да не се провали или да доведе до грешки по време на изпълнение, е причината статичното въвеждане да е хубаво.   -  person C. A. McCann    schedule 07.11.2010
comment
съжалявам, че просто не го купувам като полезна функция. с тестването можете да помислите какво е на мястото си и как може да се счупи, вместо да стеснявате ума си, за да накарате кода си да работи само по един начин, без да отчитате външните влияния.   -  person Steven    schedule 08.11.2010
comment
@Steven: Лично аз предпочитам да прекарам малко повече време, за да напиша код, който не може да се счупи, вместо да пиша ненадежден код и след това да губя време в писане на тривиални тестове и да се тревожа за всичко, което може да се обърка... но на всеки своето, предполагам!   -  person C. A. McCann    schedule 08.11.2010
comment
Благодаря за вашите мнения, момчета, дайте го във форум :-)   -  person 0atman    schedule 08.11.2010
comment
наистина зависи от това дали пишете код, който е малък и самостоятелен (без io, без възможно лудо въвеждане от страна на потребителя). в по-големите случаи никога няма да се сетите за всичко и вашето предполагаемо пълно доказателство изведнъж ще се окаже непълно... както и да е, пишете ми, ако искате да чатите по имейл или каквото и да е   -  person Steven    schedule 09.11.2010
comment
Може би Liquid Haskell може да бъде от полза тук?   -  person Cameron Martin    schedule 17.08.2014
comment
Трябва да добавя, че поради това, че „рамките“ са случай на монолитен ексклузивизъм (анти-модел за разработка на софтуер), а сцената за уеб разработка е доминирана от напр. WhatWG (основен пример, с техния оксиморон „жив/променлив стандарт“, който се основаваше на спагети код на браузъра, вместо да базира кода на чист стандарт) и подобни арогантни хакове, уеб рамките са просто за олицетворението на непрофесионализма. Така че, докато това със сигурност означава, че езикът е „реален свят“, това също означава, че той вече е заразен от лоши разработчици и техните отровни нагласи.   -  person Evi1M4chine    schedule 30.08.2016
comment
Важно допълнение: Каква полза от вашите доказателства, ако нямате доказателства за системата, в която ги ги пишете? … → В крайна сметка винаги ще стигнете до теоремата за непълнотата на Гьодел. … Така че всичко, което имате там, е незначително по-висока статистическа вероятност за правилност и огромно количество доказателствен театър/самозаблуда. (вж. „театър за сигурност“) … → Намерих за по-умно просто да напиша програма три пъти, по най-различни начини, които можете да намерите, и да ги използвам по излишен начин, като при самолетни компютри и RAID хранилище.   -  person Evi1M4chine    schedule 30.08.2016
comment
Да, виждам, че чак надолу са костенурки!   -  person 0atman    schedule 30.08.2016
comment
Въпреки че вярвам, че доказаният ADA е много популярен в авиоиндустрията. Те са много предпазливи, използват тройно излишен, доказан код. Добре за всички нас, предполагам!   -  person 0atman    schedule 30.08.2016
comment
Написах това: madsbuch.com/blog/proving-stuff-in-haskell може би може да помогне?   -  person Mads Buch    schedule 30.10.2016


Отговори (11)


Е, няколко неща, с които да започнете, тъй като поемате по пътя на Haskell:

  • Запознати ли сте с кореспонденцията между Къри и Хауърд? Има системи, използвани за машинно проверени доказателства, базирани на това, които в много отношения са просто функционални езици за програмиране с много мощни типови системи.

  • Запознати ли сте с областите на абстрактната математика, които предоставят полезни концепции за анализиране на код на Haskell? Различни разновидности на алгебрата и някои части от теорията на категориите се появяват много.

  • Имайте предвид, че Haskell, както всички езици, пълни с Turing, винаги има възможност за непрекратяване. Като цяло е много по-трудно да се докаже, че нещо винаги ще бъде вярно, отколкото да се докаже, че или нещо ще е вярно, или ще зависи от непреходна стойност.

Ако сериозно търсите доказателство, а не просто тестване, това са нещата, които трябва да имате предвид. Основното правило е следното: Накарайте невалидните състояния да причинят грешки на компилатора. Първоначално предотвратете кодирането на невалидни данни, а след това оставете програмата за проверка на типа да свърши досадната част вместо вас.

Ако искате да отидете още по-далеч, ако паметта не ме лъже, асистентът за доказателство Coq има „екстракт от Haskell", която ще ви позволи да докажете произволни свойства за критични функции, след което да превърнете доказателствата в код на Haskell.

За извършване на фантастични системни неща директно в Haskell, Олег Кисельов е Великият майстор. На неговия сайт можете да намерите примери за хитри трикове като полиморфни типове от по-висок ранг за кодиране на статични доказателства за проверка на границите на масив.

За по-леки неща можете да правите неща като използвайки сертификат на ниво тип, за да маркирате част от данните като проверени за коректност. Вие все още сте сами за самата проверка на коректността, но друг код може поне да разчита на знанието, че някои данни всъщност са били проверени.

Друга стъпка, която можете да предприемете, изграждайки лека проверка и фантастични системни трикове, е да използвате факта, че Haskell работи добре като хост език за вграждане на специфични за домейна езици; първо конструирайте внимателно ограничен подезик (в идеалния случай такъв, който не е пълен по Тюринг), за който можете по-лесно да докажете полезни свойства, след това използвайте програми в този DSL, за да предоставите ключови части от основната функционалност във вашата цялостна програма. Например, бихте могли да докажете, че функция с два аргумента е асоциативна, за да оправдаете паралелно намаляване на колекция от елементи, използващи тази функция (тъй като подреждането на приложението на функцията няма значение, а само подреждането на аргументите).


О, едно последно нещо. Няколко съвета за избягване на капаните, които Haskell наистина съдържа, които могат да саботират код, който иначе би бил безопасен чрез конструиране: Вашите заклети врагове тук са общата рекурсия, IO монадата и частични функции:

  • Последното е относително лесно да се избегне: не ги пишете и не ги използвайте. Уверете се, че всеки набор от съвпадения на шаблони обработва всеки възможен случай и никога не използвайте error или undefined. Единствената трудна част е избягването на стандартни библиотечни функции, които могат да причинят грешки. Някои очевидно не са безопасни, като fromJust :: Maybe a -> a или head :: [a] -> a, но други може да са по-фини. Ако откриете, че пишете функция, която наистина, наистина не може да направи нищо с някои входни стойности, тогава вие позволявате невалидните състояния да бъдат кодирани от типа вход и първо трябва да поправите това.

  • Второто е лесно да се избегне на повърхностно ниво чрез разпръскване на неща чрез различни чисти функции, които след това се използват от IO израз. По-добре е, доколкото е възможно, да преместите цялата програма в чист код, така че да може да бъде оценена независимо с всичко, освен с действителния I/O. Това най-вече става трудно само когато имате нужда от рекурсия, управлявана от външен вход, което ме отвежда до последния елемент:

  • Думи за мъдрите: Добре обоснована рекурсия и продуктивна ядрена курсия. Винаги се уверявайте, че рекурсивните функции или преминават от някаква начална точка към известен базов случай, или генерират поредица от елементи при поискване. В чистия код най-лесният начин да направите това е или чрез рекурсивно свиване на крайна структура от данни (напр. вместо функция, която се извиква директно, докато увеличава брояч до някакъв максимум, създайте списък, съдържащ диапазона от стойности на брояча, и го сгънете ) или рекурсивно генериране на мързелива структура от данни (напр. списък с прогресивни приближения към някаква стойност), като същевременно внимателно никога не смесвате двете директно (напр. не просто „намерете първия елемент в потока, отговарящ на някакво условие“; може да не Вместо това вземете стойности от потока до някаква максимална дълбочина, след това потърсете в крайния списък, като обработите по подходящ начин ненамерения случай).

  • Комбинирайки последните два елемента, за частите, където наистина се нуждаете от IO с обща рекурсия, опитайте се да изградите програмата като инкрементални компоненти, след което кондензирайте всички неудобни битове в една функция "драйвер". Например, можете да напишете GUI цикъл на събития с чиста функция като mainLoop :: UIState -> Events -> UIState, тест за излизане като quitMessage :: Events -> Bool, функция за получаване на чакащи събития getEvents :: IO Events и функция за актуализиране updateUI :: UIState -> IO (), след което всъщност да стартирате нещото с обобщена функция като runLoopIO :: (b -> a -> b) -> b -> IO a -> (b -> IO ()) -> IO (). Това поддържа сложните части наистина чисти, като ви позволява да изпълнявате цялата програма със скрипт за събитие и да проверявате полученото състояние на потребителския интерфейс, като същевременно изолирате неудобните рекурсивни I/O части в една абстрактна функция, която е лесна за разбиране и често неизбежно ще бъде правилна от parametricity.

person C. A. McCann    schedule 02.11.2010
comment
Благодаря, смятате ли, че е възможно директно да се провери моделът на Haskell? - person 0atman; 02.11.2010
comment
@Oatman: В общия случай, точно толкова, колкото е възможно да се моделира-проверява всеки език за програмиране. Много от техниките, които могат да се използват за намаляване или опростяване на пространството на състоянието за изследване, са лесни или автоматични в Haskell, поради чистотата и безопасността на типа. Въпреки това, мисленето от гледна точка на държавни преходи изобщо може да бъде контрапродуктивно тук. - person C. A. McCann; 02.11.2010
comment
@Oatman: Наистина, основното предимство на Haskell за тази цел е колко много можете да правите без дори да се нуждаете от външни инструменти. Просто избягвайте IO, обща рекурсия и частични функции, когато е възможно (което за повечето програми е почти навсякъде, с малко усилия). - person C. A. McCann; 02.11.2010
comment
@camccann: Харесва ми идеята за специфичен домейн. Диалект на Haskell, създаден да бъде доказуем! Това ли е нещо, което ще трябва да направя, или вече е свършена работа по нещо подобно, доколкото знаете? - person 0atman; 02.11.2010
comment
@Oatman: Господи, програмистите на Haskell обичат коректността на програмата. Можете да намерите малки фрагменти от този вид неща навсякъде; например, целият смисъл на монадата IO е да осигури гарантирано подреждане на страничните ефекти въпреки мързеливата оценка, без да позволява на чистия код да наблюдава нечистотии. Но за по-задълбочен пример, погледнете ImProve на Hackage. - person C. A. McCann; 02.11.2010
comment
Coq наистина има екстракт от функцията на Haskell - person Tom Crockett; 03.11.2010

Вероятно най-близкото нещо до това, което питате, е Haskabelle, инструмент, който идва с асистента за доказване Isabelle, който може да превежда файлове на Haskell в теориите на Изабел и ви позволява да докажете неща за тях. Доколкото разбирам, този инструмент е разработен в рамките на проекта HOL - ML - Haskell и страницата с документация съдържа малко информация за теорията зад нея.

Не съм много запознат с този проект и не знам много какво е направено с него. Но знам, че Брайън Хъфман си играе с тези неща, вижте документите му и разговори, те трябва да съдържат подходящи неща.

person svenningsson    schedule 02.11.2010
comment
Благодаря, ще го проверя, въпреки че наистина търся начин за проверка на модела на чист език. Преводът от по-официален език на Haskell е неоптимален за обща употреба. - person 0atman; 03.11.2010
comment
Не, обратното е. Хаскел се превежда на Изабел. Все още може да не е оптимално, но вероятно е по-добро за вашите цели от подхода на Coq за генериране на Haskell. - person svenningsson; 03.11.2010
comment
О, о, ооо! Съжалявам, че грешно прочетох отговора ви. Хм, това е необичайно и по-полезно! - person 0atman; 03.11.2010

Не съм сигурен дали това, което искаш, наистина ще те направи щастлив. :-)

Проверката на модела на език с общо предназначение е почти невъзможна, тъй като моделите трябва да са специфични за домейна, за да бъдат практични. Поради теоремата за непълнотата на Гьодел, просто няма няма метод за автоматично намиране на доказателства в достатъчно изразителна логика.

Това означава, че трябва сами да пишете коректури, което повдига въпроса дали усилията си заслужават отделеното време. Разбира се, усилията създават нещо много ценно, а именно увереността, че вашата програма е правилна. Въпросът не е дали това е задължително, а дали отделеното време е твърде голям разход. Работата с доказателствата е, че докато може да имате "интуитивно" разбиране, че програмата ви е правилна, често е много трудно да формализирате това разбиране като доказателство. Проблемът с интуитивното разбиране е, че е силно податлив на случайни грешки (печатни и други глупави грешки). Това е основната дилема при писането на правилни програми.

Така че изследването на коректността на програмите е свързано с улесняване на формализиране на доказателства и автоматична проверка на коректността им. Програмирането е неразделна част от "лекотата на формализиране"; много е важно да пишете програми в стил, който е лесен за разсъждение. В момента имаме следния спектър:

  • Императивен език като C, C++, Fortran, Python: Много е трудно да се формализира нещо тук. Единичните тестове и общите разсъждения са единственият начин да получите поне някаква увереност. Статичното писане улавя само тривиални бъгове (което е много по-добре, отколкото да не ги улавя!).

  • Чисто функционални езици като Haskell, ML: Expressive type system помага за улавяне на нетривиални бъгове и грешки. Доказването на правилността на ръка е практично за фрагменти до някъде около 200 реда, бих казал. (Направих доказателство за моя оперативен пакет, например.) Quickcheck тестването е евтин заместител на формализираните доказателства.

  • Зависимо въведени езици и асистенти за доказателство като Agda, Epigram, Coq: Доказването на правилността на цели програми е възможно благодарение на автоматизирана помощ с формализиране и откриване на доказателство. Тежестта обаче все още е голяма.

По мое мнение, текущото сладко място за писане на правилни програми е чисто функционалното програмиране. Ако животите зависят от правилността на вашата програма, по-добре преминете ниво по-високо и използвайте асистент за доказване.

person Heinrich Apfelmus    schedule 02.11.2010
comment
За да бъдете евентуално грешен педант, вероятно имате предвид липса на ефективен метод, тъй като човек може просто да изброи изчерпателно и да тества последователно по-дълги доказателства, вероятно подложени на някакво нарастващо количество гориво, за да се избегнат спиращи проблеми :-) - person sclv; 02.11.2010
comment
@sclv: Ето един метод за решаване на проблема със спирането: просто стартирайте програмата за неопределено време и изчакайте да видите дали някога ще спре. Как да разберете кога да спрете да изброявате доказателства? Накратко, можете да търсите доказателства по този начин, но не можете да различите липса на доказателство от ненамерено доказателство (все още). - person C. A. McCann; 02.11.2010
comment
Избрах подходящо определение на метода, което вече включва съображенията за ефективност. ;-) - person Heinrich Apfelmus; 02.11.2010
comment
Освен това библиотеката на Haskell SmallCheck ще изчерпателно проверете свойство за диапазон от стойности на Enum. Много практично за еднократни (без преизчисляване за всяка компилация) доказателства за някои свойства на Enum. - person recursion.ninja; 23.06.2014

Звучи сякаш искате ESC/Haskell: http://research.microsoft.com/en-us/um/people/simonpj/papers/verify/index.htm

О, и Agda вече има уеб рамка (поне доказателство за концепцията): http://www.reddit.com/r/haskell/comments/d8dck/lemmachine_a_web_framework_in_agda/

person sclv    schedule 02.11.2010
comment
ESC/Haskell изглежда добре, въпреки че реализацията е теоретична и малко ограничена, въз основа на моя преглед на хартията. Страхотен ресурс обаче - благодаря! - person 0atman; 02.11.2010
comment
Някаква идея кога ESC/Haskell ще бъде наличен в ghc? - person dan_waterworth; 09.01.2011
comment
@dan_waterworth -- Не знам за планове в тази посока, за съжаление. - person sclv; 09.01.2011
comment
жалко, наистина мога да го направя за проект, върху който работя. Благодаря за отговора. - person dan_waterworth; 09.01.2011

Разгледахте ли Quickcheck? Може да предложи някои от нещата, от които се нуждаете.

http://www.haskell.org/haskellwiki/Introduction_to_QuickCheck

person Jonno_FTW    schedule 02.11.2010
comment
Да, мисля, че изглежда фантастично и определено ще бъде основа за по-нататъшни изследвания за мен. - person 0atman; 02.11.2010
comment
smallcheck също - smallcheck е като quickcheck, но се фокусира върху изчерпателно тестване на малки случаи, за конфигурируема представа за малки. - person mokus; 02.11.2010
comment
Спомням си също така, че видях пакет някъде, който автоматично ще произвежда тестове за бърза проверка, базирани на алгебрични свойства, като например посочване на закон за разпределение за две функции, ще доведе до проверки, че f x (g y z) винаги е равно на g (f x y) (f x z). - person C. A. McCann; 02.11.2010
comment
Вижте също Lazy Smallcheck, който използва мързеливата оценка на Haskell. - person Robin Green; 10.06.2012

Вашият привидно прост пример, add(a,b), всъщност е труден за проверка - плаваща запетая, препълване, препълване, прекъсвания, проверен ли е компилаторът, проверен ли е хардуерът и т.н.

Навик е опростен диалект на Haskell, който позволява доказване на свойствата на програмата.

Hume е език с 5 нива, всяко по-ограничено и следователно по-лесно за проверка:

Full Hume
  Full recursion
PR−Hume
  Primitive Recursive functions
Template−Hume
  Predefined higher−order functions
  Inductive data structures
  Inductive  Non−recursive first−order functions
FSM−Hume
  Non−recursive data structures
HW−Hume
  No functions
  Non−recursive data structures

Разбира се, най-популярният метод днес за доказване на свойствата на програмата е модулното тестване, което осигурява силни теореми, но тези теореми са твърде специфични. "Типове, считани за вредни", Пиърс, слайд 66

person ja.    schedule 03.11.2010
comment
Аритметиката може да бъде проблем. Теоретично, ако знаете размера на всички резултати, включително междинните, можете да направите гаранции. На практика много изчисления не се поддават на това. Без цели числа с произволен размер доказването на правилната аритметика е трудно. За плаваща запетая помислете за docs.sun.com/source/806-3568/ ncg_goldberg.html (Какво всеки компютърен учен трябва да знае за плаващата запетая) и че това е след като много умни хора се опитаха да направят плаващата запетая податлива. - person David Thornley; 04.11.2010

Разгледайте Zeno. Цитирам wiki страницата:

Zeno е автоматизирана система за доказване на свойствата на програмата на Haskell; разработен в Imperial College London от William Sonnex, Sophia Drossopoulou и Susan Eisenbach. Той има за цел да реши общия проблем с равенството между два члена на Haskell за всяка входна стойност.

Много инструменти за проверка на програмата, налични днес, са от сорта за проверка на модели; в състояние да прекоси много голямо, но ограничено пространство за търсене много бързо. Те са много подходящи за проблеми с голямо описание, но без рекурсивни типове данни. Zeno от друга страна е проектиран да доказва индуктивно свойства в безкрайно пространство за търсене, но само тези с малка и проста спецификация.

person Petr    schedule 17.10.2012

В сравнение с отговора на Нейтън Фегер това би било по-ефективно - не непременно; зависи от това как DB машината го обработва. Всъщност бих очаквал да се представи малко по-зле, тъй като все още трябва да сканирате всички стойности на rec_id в двете таблици, но вашата заявка трябва да извлече три максимални стойности, докато тази на Нейтън трябва да извлече само една.
person Fred Foo    schedule 02.11.2010
comment
Бих искал да добавя, че поне се чувствах като механичен доказващ теорема на изпита ;) - person Fred Foo; 02.11.2010
comment
О, вторият резултат в Google за mizar haskell е ТОЗИ ВЪПРОС!!1 :-( - person 0atman; 02.11.2010
comment
Това беше бърз преглед на Google и CiteseerX. Тази статия на Mizar е публикувана в академично списание, макар и не високопоставено. - person Fred Foo; 02.11.2010
comment
@Oatman: Това не означава много. Stack Overflow има луд сок от Google. Въпросите за нишови теми могат лесно да победят първичните източници. - person C. A. McCann; 02.11.2010
comment
Съвсем правилно, момчета, но информацията за mizar е много слаба на земята, а не за предишния пакет, на който се надявах. Ще го добавя към списъка си за четене! - person 0atman; 02.11.2010

Някои съвсем скорошни усилия на MSR Cambridge: http://research.microsoft.com/en-us/um/people/simonpj/papers/verify/hcc-popl.pdf

person rsinha    schedule 16.08.2012

Инструментът AProVE е (поне) в състояние да докаже прекратяване на Haskell програми, което е част от доказването коректност. Повече информация можете да намерите в този документ (по-кратка версия).

Освен това може да се интересувате от зависими типове. Тук системата от типове е разширена и използвана, за да направи невъзможни грешни програми.

person C-Otto    schedule 17.10.2012

Можете да използвате инструмента hs-to-coq, за да конвертирате Haskell най-вече автоматично в Coq и след това да използвате пълната мощност на асистента за доказване на Coq за проверка на вашия Haskell код. Вижте документите https://arxiv.org/abs/1803.06960 и https://arxiv.org/abs/1711.09286 за малко повече информация.

person Joachim Breitner    schedule 24.03.2018