Joining tables

Two body atoms mean two tables. Reachability in exactly two stops, from Part 2’s metro:

reach(X, Y) :- line(X, Z), line(Z, Y).

Pair every edge with every edge, line × line: columns #1 #2 come from the first, #3 #4 from the second. The shared variable Z forces #2 = #3. The head keeps the endpoints X and Y, columns #1 and #4:

reach := π#1, #4( σ#2 = #3( line × line ) )
A join is product then filter: every variable shared between atoms becomes an equality between the columns it sits in.