prologmeta-predicate

Abstracting over predicates


An exercise I'm trying out starts with the following facts

byCar(auckland,hamilton).
byCar(hamilton,raglan).
byCar(valmont,saarbruecken).
byCar(valmont,metz).

byTrain(metz,frankfurt).
byTrain(saarbruecken,frankfurt).
byTrain(metz,paris).
byTrain(saarbruecken,paris).

byPlane(frankfurt,bangkok).
byPlane(frankfurt,singapore).
byPlane(paris,losAngeles).
byPlane(bangkok,auckland).
byPlane(singapore,auckland).
byPlane(losAngeles,auckland).

...and asks the reader to define a predicate travel/3 such that, for example,

travel(valmont, losAngeles, T)

...will find solutions like

T = go(byCar(valmont, metz),
       go(byTrain(metz, paris),
          go(byPlane(paris, losAngeles)))).

This what I came up with:

travel(X,Y,go(byCar(X,Y))):-byCar(X,Y).
travel(X,Y,go(byTrain(X,Y))):-byTrain(X,Y).
travel(X,Y,go(byPlane(X,Y))):-byPlane(X,Y).

travel(X,Z,go(byCar(X,Y),T)):-byCar(X,Y),travel(Y,Z,T).
travel(X,Z,go(byTrain(X,Y),T)):-byTrain(X,Y),travel(Y,Z,T).
travel(X,Z,go(byPlane(X,Y),T)):-byPlane(X,Y),travel(Y,Z,T).

It seems to work...

?- travel(valmont, losAngeles, X).
X = go(byCar(valmont, saarbruecken), go(byTrain(saarbruecken, paris), go(byPlane(paris, losAngeles)))) ;
X = go(byCar(valmont, metz), go(byTrain(metz, paris), go(byPlane(paris, losAngeles)))) ;
false.

...but it hurts my eyes; all that repetition is a cry for abstraction.

I tried to eliminate the repetition by defining

oneLeg(X,Y):-byCar(X,Y);byTrain(X,Y);byPlane(X,Y).

...and redefining travel/3 as

travel(X,Y,go(oneLeg(X,Y))):-oneLeg(X,Y).
travel(X,Z,go(oneLeg(X,Y),T)):-oneLeg(X,Y),travel(Y,Z,T).

...but the results are not quite there yet:

?- travel(valmont, losAngeles, X).
X = go(oneLeg(valmont, saarbruecken), go(oneLeg(saarbruecken, paris), go(oneLeg(paris, losAngeles)))) ;
X = go(oneLeg(valmont, metz), go(oneLeg(metz, paris), go(oneLeg(paris, losAngeles)))) ;
false.

How can I force the replacement of instances of oneLeg in the result with the specific byCar, byTrain, or byPlane that "justifies" the oneLeg instance?


Solution

  • firstACommentOnNamingThingsasInJavaByMixingTheCasesWhichIsHardToRead: you_may_find_even_long_names_very_readable_when_using_underscores.

    Second, Prolog is an extremely dynamic language, and you can easily construct and call arbitrary closures using the call/N family of meta-predicates, and other higher-order predicates like (=..)/2.

    In your example, consider first changing the predicate names to fit the usual Prolog convention for naming, using underscores to separate words:

    by_car(auckland, hamilton).
    by_car(hamilton, raglan).
    by_car(valmont, saarbruecken).
    by_car(valmont, metz).
    
    by_train(metz, frankfurt).
    by_train(saarbruecken, frankfurt).
    by_train(metz, paris).
    by_train(saarbruecken, paris).
    
    by_plane(frankfurt, bangkok).
    by_plane(frankfurt, singapore).
    by_plane(paris, los_angeles).
    by_plane(bangkok, auckland).
    by_plane(singapore, auckland).
    by_plane(losAngeles, auckland).
    

    Now, a suitable abstraction may be the predicate means/1, which we could define like this:

    means(plane).
    means(train).
    means(car).
    

    It is easy to use this to dynamically invoke the suitable predicates:

    by_means(From, To, Means) :-
            means(Means),
            atom_concat(by_, Means, Pred),
            call(Pred, From, To).
    

    One way to use this could look like this:

    route(To, To)   --> [].
    route(From, To) --> [Step],
            { by_means(From, Next, Means),
              Step =.. [Means,From,Next] },
            route(Next, To).
    

    Sample query and answer:

    ?- phrase(route(valmont, los_angeles), Rs).
    Rs = [car(valmont, saarbruecken), train(saarbruecken, paris), plane(paris, los_angeles)] ;
    Rs = [car(valmont, metz), train(metz, paris), plane(paris, los_angeles)] ;
    false.
    

    The key to this lies in the systematic naming convention and correspondence of means to predicates. In this case, the correspondence is constructed dynamically to illustrate several concepts at once. To increase efficiency, flexibility, and possibly even safety, you can of course encode the correspondence itself as well via static Prolog facts. For example:

    means_predicate(plane, by_plane).
    means_predicate(train, by_train).
    means_predicate(car, by_car).
    
    by_means(From, To, Means) :-
            means_predicate(Means, Pred),
            call(Pred, From, To).