Zoeken…


Invoering

Veel moderne Prolog-systemen zijn voortdurend in ontwikkeling en hebben nieuwe functies toegevoegd om klassieke tekortkomingen van de taal aan te pakken. Helaas introduceren veel Prolog-studieboeken en zelfs onderwijscursussen nog steeds alleen de verouderde prolog. Dit onderwerp is bedoeld om te illustreren hoe moderne Prolog enkele van de problemen en nogal wrede syntaxis heeft overwonnen die in oudere Prolog voorkomt en nog steeds kan worden geïntroduceerd.

CLP (FD) voor geheel getal rekenen

Traditioneel voerde Prolog rekenkunde uit met de operatoren is en =:= . Verschillende huidige Prologs bieden echter CLP (FD) (Constraint Logic Programming over Finite Domains) als een schoner alternatief voor integer rekenen. CLP (FD) is gebaseerd op het opslaan van de beperkingen die van toepassing zijn op een geheel getal en deze samen in het geheugen combineren.

CLP (FD) is een extensie in de meeste Prologs die het ondersteunt, dus moet expliciet worden geladen. Zodra het is geladen, kan de syntaxis #= de plaats innemen van is en =:= . In SWI-Prolog bijvoorbeeld:

?- X is 2+2.
X = 4.

?- use_module(library(clpfd)).
?- X #= 2+2.
X = 4.

Anders dan is , kan #= eenvoudige vergelijkingen oplossen en in beide richtingen verenigen:

?- 4 is 2+X.
ERROR: is/2: Arguments are not sufficiently instantiated

?- 4 #= 2+X.
X = 2.

CLP (FD) biedt zijn eigen syntaxis van de generator.

?- between(1,100,X).
X = 1;
X = 2;
X = 3...

?- X in 1..100.
X in 1..100.

Merk op dat de generator niet werkt: alleen de bereikbeperking wordt opgeslagen, klaar om later te worden gecombineerd. De generator kan worden gedwongen te lopen (en brute kracht beperkingen) met het label predikaat:

?- X in 1..100, label([X]).
X = 1;
X = 2;
X = 3..

Het gebruik van CLP kan een intelligente vermindering van brute force-gevallen mogelijk maken. Bijvoorbeeld met behulp van ouderwetse integer rekenkunde:

?- trace.
?- between(1,10,X), Y is X+5, Y>10.
...
Exit: (8) 6 is 1+5 ? creep
Call: (8) 6 > 10 ? creep
...
X = 6, Y = 11; ...

Prolog loopt nog steeds door de waarden 1-5, hoewel uit de gegeven omstandigheden wiskundig kan worden aangetoond dat deze waarden niet bruikbaar kunnen zijn. CLP (FD) gebruiken:

?- X in 1..10, Y #= X+5, Y #> 10.
X is 6..10,
X+5 #= Y,
Y is 11..15.

CLP (FD) doet onmiddellijk de wiskunde en berekent de beschikbare bereiken. Als u label([Y]) toevoegt, laat X alleen de nuttige waarden 6..10 doorlopen. In dit speelgoedvoorbeeld verhoogt dit de prestaties niet, omdat met een klein bereik als 1-10 de algebra-verwerking net zo lang duurt als de lus zou hebben gedaan; maar wanneer een groter aantal getallen wordt verwerkt, kan dit de berekeningstijd aanzienlijk verkorten.

Ondersteuning voor CLP (FD) is variabel tussen Prologs. De erkende beste ontwikkeling van CLP (FD) is in SICStus Prolog, dat commercieel en duur is. SWI-Prolog en andere open Prologs hebben vaak enige implementatie. Visual Prolog neemt CLP (FD) niet op in de standaardbibliotheek, hoewel uitbreidingsbibliotheken daarvoor beschikbaar zijn.

Forall in plaats van faalgestuurde lussen

Sommige "klassieke" Prolog-studieboeken gebruiken nog steeds de verwarrende en foutgevoelige foutgestuurde lus-syntaxis waarbij een fail constructie wordt gebruikt om backtracking te forceren om een doel toe te passen op elke waarde van een generator. Om bijvoorbeeld alle nummers tot een bepaalde limiet af te drukken:

fdl(X) :- between(1,X,Y), print(Y), fail.
fdl(_).

De overgrote meerderheid van moderne Prologs heeft deze syntaxis niet langer nodig, maar biedt in plaats daarvan een predikaat van hogere orde om dit aan te pakken.

nicer(X) :- forall(between(1,X,Y), print(Y)).

Dit is niet alleen veel gemakkelijker te lezen, maar als een doel dat zou kunnen mislukken in plaats van print werd gebruikt, zou het falen correct worden gedetecteerd en doorgegeven - terwijl het falen van de doelen in een foutgestuurde lus wordt verward met het geforceerde falen dat drijft de lus.

Visual Prolog heeft een aangepaste syntactische suiker voor deze lussen, gecombineerd met functiepredikaten (zie hieronder):

vploop(X) :- foreach Y = std::fromTo(1,X) do
                 console::write(X)
             end foreach.

Hoewel dit een noodzaak voor lus lijkt, volgt het nog steeds Prolog-regels: met name elke iteratie van de foreach heeft zijn eigen toepassingsgebied.

Functiestijl Predicaten

Traditioneel werden in Prolog "functies" (met één uitvoer en gebonden invoer) geschreven als reguliere predikaten:

mangle(X,Y) :- Y is (X*5)+2.

Dit kan de moeilijkheid veroorzaken dat als een predicaat in functiestijl meerdere keren wordt genoemd, het noodzakelijk is om tijdelijke variabelen te 'doorlussen'.

multimangle(X,Y) :- mangle(X,A), mangle(A,B), mangle(B,Y).

In de meeste Prologs is het mogelijk om dit te voorkomen door een alternatieve infix-operator te schrijven die moet worden gebruikt in plaats van is die uitdrukkingen met de alternatieve functie uitbreidt.

% Define the new infix operator
:- op(900, xfy, <-).

% Define our function in terms of the infix operator - note the cut to avoid
% the choice falling through
R <- mangle(X) :- R is (X*5)+2, !.

% To make the new operator compatible with is..
R <- X :-
    compound(X),            % If the input is a compound/function
    X =.. [OP, X2, X3],     % Deconstruct it
    R2 <- X2,               % Recurse to evaluate the arguments
    R3 <- X3,
    Expr =.. [OP, R2, R3],  % Rebuild a compound with the evaluated arguments
    R is Expr,              % And send it to is
    !.
R <- X :- R is X, !.        % If it's not a compound, just use is directly

We kunnen nu schrijven:

multimangle(X,Y) :- X <- mangle(mangle(mangle(Y))).

Sommige moderne Prologs gaan echter verder en bieden een aangepaste syntaxis voor dit type predicaat. In Visual Prolog bijvoorbeeld:

mangle(X) = Y :- Y = ((X*5)+2).
multimangle(X,Y) :- Y = mangle(mangle(mangle(X))).

Merk op dat de <- operator en het functionele predikaat hierboven nog steeds als relaties gedragen - het is legaal dat ze keuzepunten hebben en meerdere unificatie uitvoeren. In het eerste voorbeeld voorkomen we dit met behulp van bezuinigingen. In Visual Prolog is het normaal om de functionele syntaxis voor relaties te gebruiken en worden X = (std::fromTo(1,10))*10 op de normale manier gemaakt - bijvoorbeeld het doel X = (std::fromTo(1,10))*10 slaagt met bindingen X = 10 , X = 20, X = 30, X = 40, enz.

Flow / modus verklaringen

Bij het programmeren in Prolog is het niet altijd mogelijk of wenselijk om predikaten te maken die verenigen voor elke mogelijke combinatie van parameters. Bijvoorbeeld, het predicaat between(X,Y,Z) dat uitdrukt dat Z numeriek tussen X en Y ligt. Het wordt gemakkelijk geïmplementeerd in de gevallen waarin X, Y en Z allemaal gebonden zijn (Z ligt tussen X en Y of het is niet), of waar X en Y gebonden zijn en Z vrij is (Z verenigt zich met alle getallen tussen X en Y, of het predicaat faalt als Y <X); maar in andere gevallen, zoals waar X en Z gebonden zijn en Y gratis is, is er mogelijk een oneindig aantal unificaties. Hoewel dit kan worden geïmplementeerd, zou dit meestal niet zo zijn.

Stroomdeclaratie of modusdeclaraties maken een expliciete beschrijving mogelijk van hoe predikaten zich gedragen wanneer ze worden aangeroepen met verschillende combinaties van gebonden parameters. In het geval van between zou de verklaring zijn:

%! between(+X,+Y,+Z) is semidet.
%! between(+X,+Y,-Z) is nondet. 

Elke regel geeft één potentieel aanroeppatroon voor het predicaat aan. Elk argument is versierd met + om gevallen aan te geven waar het is gebonden, of - om gevallen aan te geven waar het niet is (er zijn ook andere decoraties beschikbaar voor complexere typen zoals tupels of lijsten die mogelijk gedeeltelijk zijn gebonden). Het trefwoord na is geeft het gedrag van het predicaat in dat geval aan, en kan een van deze zijn:

  • det als het predikaat altijd slaagt zonder keuzepunt. add(+X,+Y,-Z) is bijvoorbeeld det omdat het toevoegen van twee gegeven nummers X en Y altijd precies één antwoord heeft.
  • semidet als het predikaat slaagt of faalt, zonder semidet . Zoals hierboven is between(+X,+Y,+Z) een semidet omdat Z tussen X en Y ligt of niet.
  • multi als het predikaat altijd slaagt, maar mogelijk keuzepunten heeft (maar mogelijk ook niet). factor(+X,-Y) zou bijvoorbeeld multi omdat een getal altijd minstens één factor heeft - zelf - maar mogelijk meer.
  • nondet als het predicaat kan slagen met nondet , of faalt. between(+X,+Y,-Z) is bijvoorbeeld nondet omdat er verschillende mogelijke unificaties van Z in getallen tussen X en Y kunnen zijn, of als Y <X er geen getallen tussen staan en het predicaat mislukt.

Flow- / modusverklaringen kunnen ook worden gecombineerd met argumentlabels om te verduidelijken wat termen betekenen, of met typen. Bijvoorbeeld between(+From:Int, +To:Int, +Mid:Int) is semidet .

In pure Prologs zijn flow- en modusdeclaraties optioneel en alleen gebruikt voor het genereren van documentatie, maar ze kunnen uiterst nuttig zijn om programmeurs te helpen de oorzaak van instantatiefouten te identificeren.

In Mercury zijn flow- en modusverklaringen (en typen) verplicht en worden gevalideerd door de compiler. De gebruikte syntaxis is zoals hierboven.

In Visual Prolog zijn flow- en modusverklaringen en -typen ook verplicht en is de syntaxis anders. Bovenstaande verklaring zou worden geschreven als:

between : (int From, int To, int Mid) determ (i,i,i) nondeterm (i,i,o).

De betekenis is hetzelfde als hierboven, maar met de verschillen dat:

  • De stroom- / modusverklaringen worden gescheiden van de typeaangiften (aangezien wordt aangenomen dat de stroom / modus voor een enkel predikaat niet zal variëren met type overbelasting);
  • i en o worden gebruikt voor + en - en worden gekoppeld aan de parameters op basis van bestelling;
  • De gebruikte termen zijn verschillend. det wordt procedure , semidet wordt determ en nondet wordt nondeterm ( multi nog multi ).


Modified text is an extract of the original Stack Overflow Documentation
Licentie onder CC BY-SA 3.0
Niet aangesloten bij Stack Overflow