On this page:
2.1 Overview
2.2 Ripple Procedures
ripple/  c
2.3 Step-by-Step Example
2.4 Design Implications
7.4

2 Responding to Change

With some work, an unlike-compiler% instance can support ongoing builds that perform only the work thats relevant to reported changes.

2.1 Overview

Once an unlike-compiler% instance finishes a call to compile!, it will remember the history of each asset value as it advanced to a fulfilled state.

If you call compile! again, it will simply return the same output it already prepared unless you report that there were changes.

(send compiler compile!
  #:changed (clarify/multi compiler "/etc/config")
  #:removed (clarify/multi compiler
                           "leaked-xmas-photo.png"
                           "only-production-database-backup.sql"))

Unlike add!, which can be called at any time, #:changed and #:removed assets can only be declared after a prior successful call to compile!. Each following call to compile! will adapt an underlying graph to reflect any assets that were since changed or removed. Depending on the activity, dependent assets will regress to a value they once had in an attempt to incorporate changes while minimizing rework.

First, #:removed assets are deleted from the graph, as well as any edges connecting that that asset to others. Dependent assets are unconditionally rebuilt, but changes ripple from there according to Ripple Procedures. The compiler will raise exn:fail if any of the names marked as removed are already absent from the system.

#:changed assets then regress to a value returned from delegate. This is functionally equivalent to marking an asset to rebuild from scratch. Changes will ripple to dependents according to Ripple Procedures. This process will raise exn:fail if any #:changed assets that were not marked as removed are absent from the system.

2.2 Ripple Procedures

Dependent asset values regress according to a related ripple/c procedure provided to add!.

Returns a regressed asset value due to a change in a dependency.

The first argument is the clear name of the asset that has been changed.

The second argument is the clear name of the dependent asset.

The third argument is the history of the the dependent such that the element at index N is an unlike-asset/c returned by the advance/c element at index N+1.

The ripple procedure must return an element from this list. Otherwise the compiler will ignore the value, issue a <warning, and rebuild the dependent asset from scratch.

Internally, the compiler will only propogate a change further if the regressed value is not eq? to its previous value. The first element of the asset value’s history is always eq? to its current value, so having a ripple/c procedure return the first element of the history will both maintain the current value of the asset and prevent change from propogating further.

2.3 Step-by-Step Example

(define (replace-link-node) '...)
(define (immune changed/clear dependent/clear dependent/history) (car dependent-history))
(define (rebuild changed/clear dependent/clear dependent/history) (last dependent-history))
(define (partial changed/clear dependent/clear dependent/history) replace-link-node)
 
(send compiler add! "index.html")
(send compiler add! "styles.css")
(send compiler add! "about.html" "index.html" rebuild)
(send compiler add! "contact.html" "about.html" immune)
 
(send compiler add! "styles.css" "about.html" partial)
(send compiler add! "styles.css" "contact.html" partial)
 
(send compiler compile!)

In this example we compile a website of interdependent pages and walk through the behavior of each subsequent build.

(send compiler compile! #:changed '("about.html"))

Compile assuming that "about.html" has changed.

Rebuilds both "about.html" and "index.html".

(send compiler compile! #:changed '("contact.html"))

The website is rebuilt assuming only "contact.html" has changed. The relationship between "contact.html" and "about.html" is such that the latter will not change when the former changes. Not only that, "index.html" will also not change because propagation stopped at "about.html". In this scenario, only one webpage is fully rebuilt.

(send compiler compile! #:changed '("styles.css"))

The website is rebuilt assuming only "styles.css" has changed. In this scenario, "about.html" and "contact.html" are partially rebuilt assuming that replace-link-node is in their respective histories. But "index.html" is still fully rebuilt becase "about.html" propagates change in that way.

(send compiler compile! #:removed '("about.html"))

The website is rebuilt assuming only "about.html" was removed. "about.html" sat between "index.html" and "contact.html", which means that "contact.html" is no longer a dependency for any HTML asset. The compiler will still attempt to advance all assets, but does not guarentee that the output will function as expected. This state will likely result in a broken or missing link on a web page.

"index.html" is marked to build from scratch, but not because of its declared relationship with "about.html" this time around. Asset removal causes a full rebuild of dependents. If "index.html" had dependents, the change implied by this rebuild would ripple normally.

2.4 Design Implications