|20-CS-4003-001||Organization of Programming Languages||Fall 2018|
;; Topological sort ;; Input: ((<name-of-procedure-and-object> ;; (dep-list-as-procedures))...) ;; No ifs - procedures are wired up on load (define-syntax topo (syntax-rules () ((topo ((ident (m ...)) ...)) (letrec ((ident (lambda () (set! ident (lambda () (error 'ident "is part of a cycle"))) (let ((s (append (m) ...))) (set! ident (lambda () '())) (cons 'ident s)))) ...) (append (ident) ...))))) ;; Have Hornwich depend on Durea for a cycle (define test (lambda () (topo ((Cincinnati (Cleveland Corman Dallas Durea)) (Cleveland (Calumet Corman Durango)) (Columbus (Cincinnati Cleveland Denver Detroit Echemonte Fables)) (Chicago (Corman Durea Echemonte Eagle_Creek Erasmus)) (Calumet (Durea Edwards Echemonte)) (Corman ()) (Denver (Chicago Durea Detroit Edwards)) (Dallas (Calumet Durea Echemonte Eagle_Creek)) (Durango (Detroit Edwards Gordon Gallop)) (Durea (Hornwich)) (Detroit (Hornwich)) (Edwards ()) (Echemonte (Durango Durea Detroit Edwards)) (Eagle_Creek (Gordon Gallop Gormon)) (Erasmus (Fullman Finese Gallop)) (Fullman (Detroit Edwards Finese Halpern)) (Fortnight (Finese Gordon)) (Fallow (Fullman Gordon Gallop)) (Fables (Finese Gordon)) (Finese (Gormon)) (Gordon ()) (Gallop (Detroit Gordon)) (Gormon (Edwards Gallop Hornwich)) (Harmon (Echemonte Gormon Halpern)) (Halpern (Gormon)) (Hornwich ())))))
To the left is a macro expansion for topologically sorting a partial
order. The example it is applied to uses city names to name the
objects (vertices of a graph). Associated with each city X
is a procedure (in the letrec) which returns a list of names
of cities such that no city in the list depends on city X.
The name of city X is at the head of the list returned
(all its dependent cities are behind it in the output list).
When it is first called the procedure appends the outputs of the
invocations of the procedures of all its direct dependents (saved as
s) and then the identity of the object is consed onto
s. But prior to getting a value for s the procedure
redefines itself, via the first set!, to this:
(lambda () (error 'n "is part of a cycle")))If this procedure is invoked it is because there is some chain of invocations through dependencies, beginning with procedure X, say, and back to X. This means no total ordering is possible and the error message resulting from this invocation expresses that. Just before a procedure outputs the value of (cons 'ident s) it redefines itself again via the second set! to
(lambda () '()))Any future calls to this procedure will just return without exploring an already explored part of the graph. There is no error in this case because the procedure's identity has already been output (it and its dependencies have all been taken care of and are effectively removed from the graph).
Observe that there are no if statements in the code! None are needed because, when the program is loaded, all the procedures are "wired" up so that only the procedure invocations dictate control flow and the structure of procedure calls is a product of the define-syntax expansion.
Incidentally identifiers are used both to name procedures as well as cities - in the C language we probably would have used array indices to identify the cities.