Pokračování (informatika)

Pokračování ( anglicky  continuation ) je abstraktní znázornění stavu programu v určitém okamžiku, které lze uložit a použít k přechodu do tohoto stavu. Pokračování obsahují všechny informace pro pokračování provádění programu od určitého bodu; stav globálních proměnných obvykle není zachován, ale to není u funkčních jazyků podstatné (například selektivní ukládání a obnovování hodnot globálních objektů ve Scheme je dosaženo samostatným dynamickým mechanismem větru). Pokračování jsou podobná jako goto BASICsetjmp nebo makra longjmpv C , protože také umožňují přeskočit na libovolné místo v programu. Pokračování vám ale na rozdíl od goto, umožňuje přejít pouze do části programu s určitým stavem, který je nutné předem uložit, zatímco gotoumožňuje přejít do části programu s neinicializovanými proměnnými .

První jazyk, který implementoval koncept pokračování, byl Scheme a později se vestavěná podpora pro pokračování objevila v řadě dalších jazyků.

Definice

Formálně se callcc jedná o funkci vyššího řádu , která vám umožňuje abstrahovat dynamický kontext existující funkce ve formě jiné funkce, která se nazývá „pokračování“.

Přesněji řečeno, pokračování je „zbytek programu z daného bodu“ nebo „funkce, která se nikdy nevrátí do bodu, ve kterém byla volána“ [1] . V kurzech funkcionálního programování se vysvětlení pojmu pokračování často snižuje na „rozšíření (komplikování) pojmu korutina “, ale v didaktickém smyslu je takové vysvětlení považováno za zbytečné [1] . Důvod obtížnosti vysvětlení pojmu spočívá ve skutečnosti, že pokračování jsou vlastně alternativním zdůvodněním pojmu „chování“ („volání“ v nejširším slova smyslu), to znamená, že nesou jiný sémantický model, a to Počáteční přechod od „obyčejného“ funkcionálního programování k programování s intenzivním používáním pokračování lze přirovnat k počátečnímu přechodu od imperativního k funkcionálnímu programování .

Pokračování poskytují matematický základ pro celé pořadí provádění programu , od smyčekgoto po rekurzi , výjimky , generátory , korutiny a návratový mechanismus [1] . V důsledku toho umožňují implementaci všech těchto prvků v jazyce prostřednictvím jediného konstruktu.

Pokračování absolvování stylového programování

Programování ve stylu pokračování a předávání (CPS ) je styl programování, ve kterém se řízení přenáší prostřednictvím mechanismu pokračování .  Styl CPS poprvé představili Gerald Jay Sussman a Guy Steele, Jr. , ve stejnou dobu jako jazyk Scheme .

Program "klasického stylu" může být často přepsán ve stylu pokračování. Například pro problém výpočtu přepony pravoúhlého trojúhelníku s "klasickým" Haskellovým kódem :

pow2 :: Float -> Float pow2 a = a ** 2 add :: Float -> Float -> Float add a b = a + b pyth :: Float -> Float -> Float pyth a b = sqrt ( add ( pow2 a ) ( pow2 b ))

můžete přidat jeden argument typu F, kde Fznamená funkci z návratové hodnoty původní funkce do libovolného typu x, a z tohoto libovolného typu učinit návratovou hodnotu x:

pow2' :: Float -> ( Float -> a ) -> a pow2' a cont = cont ( a ** 2 ) add' :: Float -> Float -> ( Float -> a ) -> a add' a b cont = cont ( a + b ) -- typy a -> (b -> c) a a -> b -> c jsou ekvivalentní, takže funkci CPS lze -- považovat za funkci vyššího řádu jednoho argumentu sqrt' :: Float -> ( ( Float -> a ) -> a ) sqrt' a = \ cont -> cont ( sqrt a ) pyth' :: Float -> Float -> ( Float -> a ) -> a pyth' a b cont = pow2' a ( \ a2 -> pow2' b ( \ b2 -> add' a2 b2 ( \ anb -> sqrt' anb cont )))

Ve funkci pyth'se vypočítá druhá mocnina a funkce ( výraz lambdaa ) se předá jako pokračování , přičemž jako jediný argument se použije druhá mocnina. Dále jsou všechny následující mezilehlé hodnoty vypočteny stejným způsobem. Aby bylo možné provádět výpočty jako pokračování, je nutné předat funkci jednoho argumentu, například funkci , která vrátí libovolnou hodnotu, která jí byla předána. Výraz je tedy ekvivalentní . aidpyth' 3 4 id5.0

Standardní knihovna haskell v modulu Control.Monad.Cont obsahuje typ Cont, který umožňuje používat funkce CPS v monadě. Funkce pyth'bude vypadat takto:

pow2_m :: Float -> Cont a Float pow2_m a = return ( a ** 2 ) -- funkce cont zvýší normální funkce CPS na pyth_m monad :: Float -> Float -> Cont a Float pyth_m a b = do a2 <- pow2_m a b2 <- pow2_m b anb <- cont ( add' a2 b2 ) r <- cont ( sqrt' anb ) return r

Tento modul také obsahuje funkci callCCtypu MonadCont m => ((a -> m b) -> m a) -> m a. Z typu je vidět, že jako jediný argument bere funkci, která má zase funkci jako jediný argument, přerušující další výpočty. Můžeme například přerušit další výpočty, pokud je alespoň jeden z argumentů záporný:

pyth_m :: Float -> Float -> Cont a Float pyth_m a b = callCC $ \ exitF -> do when ( b < 0 || a < 0 ) ( exitF 0.0 ) -- when :: Aplikativní f => Bool -> f () -> f () a2 <- pow2_m a b2 <- pow2_m b anb <- cont ( add' a2 b2 ) r <- cont ( sqrt' anb ) return r

Příklady CPS ve schématu:

přímý styl Pokračování ve stylu předávání
( definovat ( pyth x y ) ( sqrt ( + ( * x x ) ( * y y )))) ( definujte ( pyth& x y k ) ( *& x x ( lambda ( x2 ) ( *& y y ( lambda ( y2 ) ( +& x2 y2 ( lambda ( x2py2 ) ( sqrt& x2py2 k ))))))))
( definovat ( faktoriál n ) ( if ( = n 0 ) 1 ; NOT tail-rekurzivní ( * n ( faktoriál ( - n 1 ))))) ( definovat ( faktoriál& n k ) ( =& n 0 ( lambda ( b ) ( pokud b ; pokračuje v růstu ( k 1 ) ; v rekurzivním volání ( -& n 1 ( lambda ( nm1 ) ( faktoriál& nm1 ( lambda ( f ) ) *& n f k )))))))))
( definovat ( faktoriál n ) ( f-aux n 1 )) ( definovat ( f-aux n a ) ( if ( = n 0 ) a ; tail-rekurzivní ( f-aux ( - n 1 ) ( * n a )) )) ( definovat ( faktoriál& n k ) ( f-aux& n 1 k )) ( definovat ( f-aux& n a k ) ( =& n 0 ( lambda ( b ) ( pokud b ; pokračování zachováno ( k a ) ; v rekurzivním volání ( -& n 1 ( lambda ( nm1 ) ( *& n a ( lambda ( nta ) ( f-aux& nm1 nta k ))))))))))

V „čistém“ CPS vlastně žádná pokračování sama o sobě neexistují – každé volání se ukáže jako koncové volání . Pokud jazyk nezaručuje optimalizaci koncových hovorů ( TCO ), pak s každým vnořeným hovorem roste jak samotné pokračování, tak zásobník hovorů .  To obvykle není žádoucí, ale někdy se to zajímavým způsobem používá (například v překladači Chicken Scheme ). Kombinované použití optimalizačních strategií TCO a CPS umožňuje zcela eliminovat dynamický zásobník ze spustitelného programu. Tímto způsobem pracuje řada kompilátorů funkčních jazyků , jako je kompilátor SML/NJ pro Standard ML . callcc

Omezená a neomezená pokračování

Existuje několik typů rozšíření. Nejběžnější z nich jsou neomezená pokračování , implementovaná pomocí funkce nebo jejích analogů. Taková pokračování skutečně představují stav celého programu (nebo jednoho z jeho vláken) v určitém okamžiku. Volání takového pokračování není jako volání funkce, protože odpovídá "skoku" do uloženého stavu programu a nevrací žádnou hodnotu; takové pokračování většinou nelze volat vícekrát. Oddělená pokračování abstrahují závislost výsledku nějakého programového bloku na výsledku nějakého podvýrazu tohoto bloku. V určitém smyslu odpovídají určitému segmentu zásobníku volání, nikoli celému zásobníku. Taková pokračování lze použít jako funkce, volat je vícekrát a tak dále. Jsou abstrahovány pomocí mechanismu : obalí vnější blok, chová se jako , ale jako argument dostává nikoli globální pokračování, ale omezené - závislost hodnoty resetovaného bloku na hodnotě v místě posuvného bloku. Existují i ​​jiné odrůdy, např . call/ccshift/resetresetshiftcall/ccprompt/control

Podpora programovacího jazyka

Mnoho programovacích jazyků poskytuje tuto schopnost pod různými názvy, jako například:

  • Schéma : call/cc(zkratka pro call-with-current-continuation);
  • Standardní ML : SMLofNJ.Cont.callccimplementováno také v Concurrent ML ;
  • C : setcontexta analogy ( UNIX System V a GNU libc );
  • Ruby : callcc;
  • Smalltalk : Continuation currentDo:Ve většině moderních implementací, pokračování mohou být realizována v čistém Smalltalk bez vyžadovat zvláštní podporu ve virtuálním stroji ;
  • JavaScript : awaita yield;
  • JavaScript Rhino : Continuation;
  • Haskell : callCC(v modulu Control.Monad.Cont);
  • Faktor : callcc0a callcc1;
  • Python : yield;
  • Python PyPy : _continuation.continulet;
  • Kotlin : , na jehož základě jsou suspendimplementovány async, await, yielda některé další coroutine konstrukty .
  • Scala : existuje plugin pro podporu omezených pokračování;
  • PHP : yield;
  • C# : yield returna await.

V jakémkoli jazyce, který podporuje uzávěry , můžete psát programy ve stylu předávání pokračování a ručně implementovat call/cc. Konkrétně se jedná o přijímanou praxi v Haskellu , kde se snadno sestavují „monády, které projdou pokračováním“ (například knihovna monáda Conta monádový transformátor ). ContTmtl

Poznámky

  1. 1 2 3 Falešná vlákna, 1999 .

Odkazy