1
votes

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?

2
Check out the call/N family of higher order predicates. In your case, call/3 will be especially useful. In addition, (=..)/2 is also worth a look in your case. - mat
@mat: Thanks, but I can't figure out your suggestion... - kjo
everything will be easier if you don't split in three different tables, but have one table with three arguments: from, to, means. Then you don't have to change your program if suddenly you also need to travel by sea or something like this. - user7473772
I agree with User9213 that representing the input differently may make certain queries easier, including ones you care about in this question. However, it also comes with its own drawbacks. For example, it (albeit negligibly) enlarges the data that needs to be stored, and you may not be free to choose the representation, and not in a position to rewrite all available data. - mat
@User9213: thanks for your comment; the tables, however, were givens of the exercise I was doing. - kjo

2 Answers

3
votes

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).
2
votes

If I had to do this I would probably try to transform byCar and byPlane and byTrain to one single table, from_to_means. I find you can do this manually like this:

forall(byCar(From, To), assertz(from_to_means(From, To, car)))

and then the same for Plane and Train. In SWI-Prolog there is also term expansion so maybe you can insert above the three original tables

term_expansion(byCar(From, To), from_to_means(From, To, car)).

and the same for plane and train.

Then you only need to evaluate from_to_means(From, To, Means) or you can choose only one means of transport if you write from_to_means(From, To, train).