Semipredikat problem
Inom datorprogrammering uppstår ett semi-predikatproblem när en subrutin avsedd att returnera ett användbart värde kan misslyckas, men signaleringen av fel använder ett annars giltigt returvärde . Problemet är att den som anropar subrutinen inte kan säga vad resultatet betyder i det här fallet.
Exempel
Divisionsoperationen ger ett reellt tal , men misslyckas när divisorn är noll . Om vi skulle skriva en funktion som utför division, kan vi välja att returnera 0 på denna ogiltiga indata. Men om utdelningen är 0 blir resultatet också 0. Detta betyder att det inte finns något nummer som vi kan återgå till unikt signalerar försök till division med noll, eftersom alla reella tal ligger inom divisionsområdet .
Praktiska konsekvenser
Tidiga programmerare hanterade potentiellt exceptionella fall, som i fallet med division, med hjälp av en konvention som krävde anropsrutinen för att kontrollera giltigheten av ingångarna innan de anropade divisionsfunktionen. Detta hade två problem. För det första belastar den all kod som utför division (en mycket vanlig operation). För det andra bryter det mot om inte upprepa dig själv och inkapsling , där den förra handlar om att eliminera duplicerad kod, och den senare föreslår att dataassocierad kod ska finnas på ett ställe (i det här fallet gjordes verifieringen av inmatning separat). Om vi föreställer oss en mer komplicerad beräkning än division, kan det vara svårt för den som ringer att veta vad som anses vara ogiltig inmatning; i vissa fall kan det vara lika kostsamt att ta reda på om inmatningen är giltig som att utföra hela beräkningen. Det finns också möjligheten att målfunktionen modifieras och då förväntar sig andra förutsättningar än de som uppringaren har kollat efter; En sådan förändring skulle kräva förändringar på alla de platser där funktionen anropades från.
Lösningar
Semipredicate-problemet är inte universellt bland funktioner som kan misslyckas.
Använder en anpassad konvention för att tolka returvärden
Om funktionens omfång inte täcker hela utrymmet som motsvarar datatypen för funktionens returvärde, kan ett värde som är känt för att vara omöjligt vid normal beräkning användas. Tänk till exempel på funktionen index
, som tar en sträng och en delsträng och returnerar heltalsindexet för delsträngen i huvudsträngen. Om sökningen misslyckas kan funktionen programmeras att returnera -1 (eller något annat negativt värde), eftersom detta aldrig kan betyda ett framgångsrikt resultat.
Denna lösning har dock sina problem, eftersom den överbelastas den naturliga innebörden av en funktion med en godtycklig konvention.
- Programmeraren måste komma ihåg specifika felvärden för många funktioner, som naturligtvis inte kan vara identiska om funktionerna har olika intervall.
- En annan implementering av samma funktion kan välja att använda ett annat felvärde, vilket resulterar i möjliga buggar när programmerare flyttar från miljö till miljö.
- Om den felande funktionen vill kommunicera användbar information om varför den hade misslyckats, är ett felvärde otillräckligt.
- Ett heltal med tecken halverar det möjliga indexintervallet för att kunna lagra teckenbiten .
- Även om det valda värdet är ett ogiltigt resultat för denna operation, kan det vara en giltig indata för uppföljningsoperationer. Till exempel i Python
str.find
-1 om delsträngen inte hittas, men -1 är ett giltigt index (negativa index börjar vanligtvis från slutet).
Flervärdig avkastning
Många språk tillåter, genom en eller annan mekanism, en funktion för att returnera flera värden. Om detta är tillgängligt kan funktionen designas om för att returnera ett booleskt värde som signalerar framgång eller misslyckande, utöver dess primära returvärde. Om flera fellägen är möjliga kan funktionen istället returnera en uppräknad returkod (felkod) utöver dess primära returvärde.
Olika tekniker för att returnera flera värden inkluderar:
- Returnerar en tupel av värden. Detta är vanligt i språk som Python , som har en inbyggd tupeldatatyp och speciell syntax för att hantera dessa: i Python anropar x, y = f() funktionen f som returnerar ett par värden och tilldelar element i paret till två variabler.
- Sekundära returvärden som i Common Lisp . Alla uttryck har ett primärt värde, men sekundära värden kan returneras till intresserade uppringare. Till exempel
GETHASH
-funktionen värdet för den givna nyckeln i en associativ karta , eller ett standardvärde annars. Men den returnerar också en sekundär boolean som indikerar om värdet hittades, vilket gör det möjligt att skilja mellan fallen "inget värde hittades" och "det hittade värdet var lika med standardvärdet". Detta skiljer sig från att returnera en tuppel, eftersom sekundära returvärden är valfria – om en uppringare inte bryr sig om dem kan den ignorera dem helt, medan returer med tupelvärde bara är syntaktisk socker för att returnera och packa upp en lista, och varje uppringare måste alltid känna till och förbruka alla varor som returneras. - Språk med call by reference – eller ekvivalenter, som call by address using pointers – kan tillåta flervärdesretur genom att ange vissa parametrar som utdataparametrar . I det här fallet kan funktionen bara returnera felvärdet, med en variabel avsedd att lagra det faktiska resultatet som skickas till funktionen. Detta är analogt med användningen av en utgångsstatus för att lagra en felkod och strömmar för att returnera innehåll.
- En variant av utdataparametrar används i objektorienterade språk som använder call by sharing , där ett föränderligt objekt skickas till en funktion och objektet muteras för att returnera värden.
- Logiska programmeringsspråk som Prolog har inga returvärden. Istället används obundna logiska variabler som utdataparametrar, för att förenas med värden konstruerade i ett predikatanrop.
Global variabel för returstatus
I likhet med ett "ut"-argument kan en global variabel lagra vilket fel som uppstod (eller helt enkelt om ett fel inträffade).
Till exempel, om ett fel inträffar och signaleras (vanligen som ovan, av ett olagligt värde som −1) ställs Unix errno -variabeln in för att indikera vilket värde som inträffade.
Att använda en global har sina vanliga nackdelar: trådsäkerhet blir ett problem (moderna operativsystem använder en trådsäker version av errno), och om bara ett globalt fel används måste dess typ vara tillräckligt bred för att innehålla all intressant information om alla möjliga fel i systemet.
Undantag
Undantag är ett allmänt använt system för att lösa detta problem. Ett feltillstånd anses inte alls vara ett returvärde för funktionen; normalt styrflöde störs och explicit hantering av felet sker automatiskt. De är ett exempel på signalering utanför bandet .
Utökar returvärdetypen
Manuellt skapade hybridtyper
I C är ett vanligt tillvägagångssätt, när det är möjligt, att använda en datatyp som är avsiktligt bredare än vad som strikt behövs av funktionen. Till exempel är standardfunktionen getchar()
definierad med returtyp int
och returnerar ett värde i intervallet [0,255] (intervallet för unsigned char
) vid framgång eller värdet EOF
( implementeringsdefinierad , men utanför intervallet för unsigned char )
) i slutet av inmatningen eller ett läsfel.
Nullbara referenstyper
På språk med pekare eller referenser är en lösning att returnera en pekare till ett värde, snarare än värdet i sig. Denna returpekare kan sedan ställas in på null för att indikera ett fel. Den är vanligtvis lämpad för funktioner som ändå returnerar en pekare. Detta har en prestandafördel jämfört med OOP-stilen för undantagshantering, med nackdelen att försumliga programmerare kanske inte kontrollerar returvärdet, vilket resulterar i en krasch när den ogiltiga pekaren används. Huruvida en pekare är null eller inte är ett annat exempel på predikatproblemet; null kan vara en flagga som indikerar fel eller värdet på en pekare som returneras framgångsrikt. Ett vanligt mönster i UNIX- miljön är att ställa in en separat variabel för att indikera orsaken till ett fel. Ett exempel på detta är funktionen C standard bibliotek fopen () .
Implicit hybridtyper
I dynamiskt typade språk, som PHP och Lisp , är det vanliga tillvägagångssättet att returnera "false", "ingen" eller "null" när funktionsanropet misslyckas. Detta fungerar genom att returnera en annan typ till den normala returtypen (och därmed utöka typen). Det är en dynamiskt skriven motsvarighet till att returnera en nollpekare.
Till exempel returnerar en numerisk funktion normalt ett tal (int eller float), och medan noll kan vara ett giltigt svar; falskt är det inte. På liknande sätt kan en funktion som normalt returnerar en sträng ibland returnera den tomma strängen som ett giltigt svar, men returnera false vid fel. Denna process av typjonglering kräver noggrannhet vid testning av returvärdet: t.ex. i PHP, använd ===
(dvs lika och av samma typ) istället för bara ==
(dvs lika, efter automatisk typkonvertering). Det fungerar bara när den ursprungliga funktionen inte är avsedd att returnera ett booleskt värde, och fortfarande kräver att information om felet förmedlas på andra sätt.
Explicit hybridtyper
I Haskell och andra funktionella programmeringsspråk är det vanligt att använda en datatyp som är precis så stor som den behöver vara för att uttrycka alla möjliga resultat. Till exempel kan vi skriva en divisionsfunktion som returnerar typen Maybe Real
, och en getchar-
funktion som returnerar antingen String Char
. Den första är en alternativtyp , som bara har ett felvärde, ingenting
. Det andra fallet är en taggad union : ett resultat är antingen en sträng med ett beskrivande felmeddelande eller ett framgångsrikt läst tecken. Haskells typinferenssystem hjälper till att säkerställa att uppringare hanterar eventuella fel. Eftersom felförhållandena blir explicita i funktionstypen, talar om för programmeraren om hur man behandlar fel när man tittar på dess signatur. Dessutom bildar taggade fackföreningar och alternativtyper monader när de är utrustade med lämpliga funktioner: detta kan användas för att hålla koden snygg genom att automatiskt sprida obehandlade feltillstånd.
Se även
- ^ Norvig, Peter (1992). "Den allmänna problemlösaren". Paradigmer för programmering av artificiell intelligens: fallstudier i gemensam LISP . Morgan Kaufmann . sid. 127. ISBN 1-55860-191-0 .
- ^ "Inbyggda typer — Python 3.10.4-dokumentation" .
-
^ "Om i eller j är negativ, är indexet relativt slutet av sekvensen s:
len(s) + i
ellerlen(s) + j
ersätts." notering för vanliga sekvensoperationer (3) - ^ Varför undantag bör vara exceptionella - ett exempel på prestandajämförelse