1

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?

mat
  • 40,498
  • 3
  • 51
  • 78
kjo
  • 33,683
  • 52
  • 148
  • 265
  • 1
    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 Feb 26 '17 at 17:40
  • @mat: Thanks, but I can't figure out your suggestion... – kjo Feb 26 '17 at 18:10
  • 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. –  Mar 03 '17 at 12:40
  • 1
    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 Mar 03 '17 at 18:01
  • @User9213: thanks for your comment; the tables, however, were givens of the exercise I was doing. – kjo Mar 03 '17 at 19:25
  • @mat about enlarging data, I couldn't find how to measure this. Also, is this data in memory or on disk? And if you normalize the data, does that help? Or in other words, if you have say `x(foo, a). x(foo, b). x(foo, c).` compared to `x(1, a). x(1, b). x(1, c). ids(1, foo).` how do they look in memory? (I guess they are just text files on disk??) –  Mar 06 '17 at 08:02

2 Answers2

3

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).
mat
  • 40,498
  • 3
  • 51
  • 78
  • 1
    You are welcome! As an exercise, consider writing a version that also avoids the use of `(=..)/2`! – mat Feb 27 '17 at 14:08
  • I don't know that much about Prolog but why are you doing dynamic things and `=..` and `call`? Isn't it possible to use a single table like `conn(car, auckland, hamilton)` or `conn(plane, frankfurt, sinapore)`? Or is Prolog very different from a relational database? –  Mar 03 '17 at 12:43
  • 1
    Yes, that's of course possible, and a good alternative solution to one part of the question. Please see my comment on your answer (+1!) for more information. Please also note that it may not always be feasible to rewrite all your data, and here I took it as part of the question to still solve the abstraction issue even if the input is given in the shown form. You can solve one part by representing the data differently. However, the other issue still remains: How do you flexibly and elegantly obtain a list describing such routes? In this answer, I am using `(=..)/2` for this. – mat Mar 03 '17 at 17:58
2

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

  • 2
    This is one (good) alternative solution to a part of the question. It does not explain how to solve the second issue though: How do you turn solutions from these predicates into lists of the form `[car(valmont, metz), train(metz, paris), plane(paris, los_angeles)]`, which I show in the second part of my answer. For this, you either need again a different representation, redundant data in your tables, or use `(=..)/2`. It is clear that when you are free to reorganize the tables themselves, you can make the means part of a more uniform table. +1 for showing a good alternative representation! – mat Mar 03 '17 at 17:55
  • Hello @mat and thank you for your kind words. I really don't know Prolog well enough to judge. I thought that the data structure and how it is displayed on the screen are more separate but it seems that you always need more code to map one data structure to another if you want to not print separately to the screen. –  Mar 06 '17 at 07:58