We have formulated an idealized model of big-step multi-result supercompilation. This model is rather abstract, and yet it can be instantiated to produce runnable supercompilers.
Given an initial configuration c
, a supercompiler produces a list of "residual" graphs of configurations:
g[1], ... , g[k]
What is a "graph of configurations"?
Graphs of configurations are supposed to represent "residual programs" and are defined in Agda (see Graphs.agda
) in the following way:
data Graph (C : Set) : Set where
back : ∀ (c : C) → Graph C
forth : ∀ (c : C) (gs : List (Graph C)) → Graph C
Technically, a Graph C
is a tree, with back
nodes being
references to parent nodes.
A graph's nodes contain configurations. Here we abstract away from the concrete structure of configurations. In this model the arrows in the graph carry no information, because, this information can be kept in nodes. (Hence, this information is supposed to be encoded inside "configurations".)
To simplify the machinery, back-nodes in this model of
supercompilation do not contain explicit references
to parent nodes. Hence, back c
means that c
is foldable
to a parent configuration (perhaps, to several ones).
-
Back-nodes are produced by folding a configuration to another configuration in the history.
-
Forth-nodes are produced by
-
decomposing a configuration into a number of other configurations (e.g. by driving or taking apart a let-expression), or
-
by rewriting a configuration by another one (e.g. by generalization, introducing a let-expression or applying a lemma during two-level supercompilation).
-
The knowledge about the input language a supercompiler deals with is represented by a "world of supercompilation", which is a record that specifies the following.
-
Conf
is the type of "configurations". Note that configurations are not required to be just expressions with free variables! In general, they may represent sets of states in any form/language and as well may contain any additional information. -
_⊑_
is a "foldability relation".c ⊑ c′
means thatc
is foldable toc′
. (In such casesc′
is usually said to be "more general" thanc
.) -
_⊑?_
is a decision procedure for_⊑_
. This procedure is necessary for implementing supercompilation in functional form. -
_⇉
is a function that gives a number of possible decompositions of a configuration. Letc
be a configuration andcs
a list of configurations such thatcs ∈ c ⇉
. Thenc
can be "reduced to" (or "decomposed into") configurationscs
.
Suppose that driving is deterministic and, given a configuration c
,
produces a list of configurations c ⇊
. Suppose that rebuilding
(generalization, application of lemmas) is non-deterministic and
c ↷
is the list of configurations that can be produced by
rebuilding. Then (in this special case) _⇉
can be implemented as
follows:
c ⇉ = [ c ⇊ ] ++ map [_] (c ↷)
whistle
is a "bar whistle" (seeBarWhistle.agda
) that is used to ensure termination of functional supercompilation.
Thus we have the following definition in Agda:
record ScWorld : Set₁ where
field
Conf : Set
_⊑_ : (c c′ : Conf) → Set
_⊑?_ : (c c′ : Conf) → Dec (c ⊑ c′)
_⇉ : (c : Conf) → List (List Conf)
whistle : BarWhistle Conf
open BarWhistle whistle public
History : Set
History = List Conf
Foldable : ∀ (h : History) (c : Conf) → Set
Foldable h c = Any (_⊑_ c) h
foldable? : ∀ (h : History) (c : Conf) → Dec (Foldable h c)
foldable? h c = Any.any (_⊑?_ c) h
Note that, in addition to (abstract) fields, there are a few concrete type and function definitions.
-
History
is a list of configuration that have been produced in order to reach the current configuration. -
Foldable h c
means thatc
is foldable to a configuration in the historyh
. -
foldable? h c
decides whetherFoldable h c
.
If we need labeled edges in the graph of configurations, the labels can be hidden inside configurations. (Recall that "configurations" do not have to be just symbolic expressions, as they can contain any additional information.)
Here is the definition in Agda of words of supercompilation with labeled edges:
record ScWorldWithLabels : Set₁ where
field
Conf : Set -- configurations
Label : Set -- edge labels
_⊑_ : (c c′ : Conf) → Set -- c is foldable to c′
_⊑?_ : (c c′ : Conf) → Dec (c ⊑ c′) -- ⊑ is decidable
-- Driving/splitting/rebuilding a configuration:
_⇉ : (c : Conf) → List (List (Label × Conf))
whistle : BarWhistle Conf -- a bar whistle
There is defined (in BigStepSc.agda
) a function
injectLabelsInScWorld : ScWorldWithLabels → ScWorld
that injects a world with labeled edges into a world without labels (by hiding labels inside configurations).
In BigStepSc.agda
there is given a relational definition of
non-deterministic supercompilation in terms of two relations
infix 4 _⊢NDSC_↪_ _⊢NDSC*_↪_
data _⊢NDSC_↪_ : ∀ (h : History) (c : Conf) (g : Graph Conf) → Set
_⊢NDSC*_↪_ : ∀ (h : History) (cs : List Conf) (gs : List (Graph Conf)) → Set
which are defined with respect to a world of supercompilation.
Let h
be a history, c
a configuration and g
a graph. Then
h ⊢NDSC c ↪ g
means that g
can be produced from h
and c
by
non-deterministic supercompilation.
Let h
be a history, cs
a list of configurations, gs
a list
of graphs, and length cs = length gs
. Then h ⊢NDSC* cs ↪ gs
means that each g ∈ gs
can be produced from the history h
and the corresponding c ∈ cs
by non-deterministic supercompilation.
Or, in Agda:
h ⊢NDSC* cs ↪ gs = Pointwise.Rel (_⊢NDSC_↪_ h) cs gs
⊢NDSC_↪_
is defined by two rules
data _⊢NDSC_↪_ where
ndsc-fold : ∀ {h : History} {c}
(f : Foldable h c) →
h ⊢NDSC c ↪ back c
ndsc-build : ∀ {h : History} {c}
{cs : List (Conf)} {gs : List (Graph Conf)}
(¬f : ¬ Foldable h c)
(i : cs ∈ c ⇉)
(s : (c ∷ h) ⊢NDSC* cs ↪ gs) →
h ⊢NDSC c ↪ forth c gs
The rule ndsc-fold
says that if c
is foldable to a configuration in h
there can be produced the graph back c
(consisting of a single back-node).
The rule ndsc-build
says that there can be produced a node forth c gs
if
the following conditions are satisfied.
c
is not foldable to a configuration in the historyh
.c ⇉
contains a list of configurationscs
, such that(c ∷ h) ⊢NDSC* cs ↪ gs
.
Speaking more operationally, the supercompiler first decides how to decompose
c
into a list of configurations cs
by selecting a cs ∈ c
. Then,
for each c ∈ cs
the supercompiler produces a graph g
, to obtain a list of
graphs gs
, and builds the graph c ↪ forth c gs
.
The main difference between multi-result and non-deterministic
supercompilation is that multi-result uses a whistle (see Whistles.agda
)
in order to ensure the finiteness of the collection of residual graphs.
In BigStepSc.agda
there is given a relational definition of
multi-result supercompilation in terms of two relations
infix 4 _⊢MRSC_↪_ _⊢MRSC*_↪_
data _⊢MRSC_↪_ : ∀ (h : History) (c : Conf) (g : Graph Conf) → Set
_⊢MRSC*_↪_ : ∀ (h : History) (cs : List Conf) (gs : List (Graph Conf)) → Set
Again, _⊢MRSC*_↪_
is a "point-wise" version of _⊢MRSC_↪_
:
h ⊢MRSC* cs ↪ gs = Pointwise.Rel (_⊢MRSC_↪_ h) cs gs
⊢MRSC_↪_
is defined by two rules
data _⊢MRSC_↪_ where
mrsc-fold : ∀ {h : History} {c}
(f : Foldable h c) →
h ⊢MRSC c ↪ back c
mrsc-build : ∀ {h : History} {c}
{cs : List Conf} {gs : List (Graph Conf)}
(¬f : ¬ Foldable h c)
(¬w : ¬ ↯ h) →
(i : cs ∈ c ⇉)
(s : (c ∷ h) ⊢MRSC* cs ↪ gs) →
h ⊢MRSC c ↪ forth c gs
We can see that ⊢NDSC_↪_
and _⊢MRSC_↪_
differ only in that there is
an additional condition ¬ ↯ h
in the rule mrsc-build
.
The predicate ↯
is provided by the whistle, ↯ h
meaning that
the history h
is "dangerous". Unlike the rule ndsc-build
,
the rule mrsc-build
is only applicable when ¬ ↯ h
, i.e.
the history h
is not dangerous.
Multi-result supercompilation is a special case of non-deterministic supercompilation, in the sense that any graph produced by multi-result supercompilation can also be produced by non-deterministic supercompilation:
MRSC→NDSC : ∀ {h : History} {c g} →
h ⊢MRSC c ↪ g → h ⊢NDSC c ↪ g
A proof of this theorem can be found in BigStepScTheorems.agda
.
Now we are going to give an alternative definition of multi-result
supercompilation in form of a total function naive-mrsc
.
The termination of naive-mrsc
is guaranteed by a "whistle".
In our model of big-step supercompilation whistles are assumed to be "inductive bars". See:
Thierry Coquand. About Brouwer's fan theorem. September 23, 2003. Revue internationale de philosophie, 2004/4 n° 230, p. 483-489.
http://www.cairn.info/revue-internationale-de-philosophie-2004-4-page-483.htm
Inductive whistles are defined in Agda in the following way.
First of all, BarWhistles.agda
contains the following declaration
of Bar D h
:
data Bar {A : Set} (D : List A → Set) :
(h : List A) → Set where
now : {h : List A} (bz : D h) → Bar D h
later : {h : List A} (bs : ∀ c → Bar D (c ∷ h)) → Bar D h
At the first glance, this declaration looks as a puzzle. But, actually, it is not as mysterious as it may seem.
We consider sequences of elements (of some type A
), and a predicate
D
. If D h
holds for a sequence h
, h
is said to be "dangerous".
Bar D h
means that either (1) h
is dangerous, i.e.
D h
is valid right now (the rule now
), or
(2) Bar D (c ∷ h)
is valid for all possible c ∷ h
(the rule later
). Hence, for any continuation c ∷ h
the sequence will eventually become dangerous.
The subtle point is that if Bar D []
is valid, it implies that
any sequence will eventually become dangerous.
A bar whistle is a record (see BarWhistles.agda
)
record BarWhistle (A : Set) : Set₁ where
field
↯ : (h : List A) → Set
↯∷ : (c : A) (h : List A) → ↯ h → ↯ (c ∷ h)
↯? : (h : List A) → Dec (↯ h)
bar[] : Bar ↯ []
where
↯
is a predicate on sequences,↯ h
meaning that the sequenceh
is dangerous.↯∷
postulates that if↯ h
then↯ (c ∷ h)
for all possiblec ∷ h
. In other words, ifh
is dangerous, so are all continuations ofh
.↯?
says that↯
is decidable.bar[]
says that any sequence eventually becomes dangerous. (In Coquand's terms,Bar ↯
is required to be "an inductive bar".)
The functional specification of big-step multi-result supercompilation
considered in the following section is based on the function
cartesian
:
cartesian : ∀ {A : Set} (xss : List (List A)) → List (List A)
cartesian
takes as input a list of lists xss
(see Util.agda
).
Each list xs ∈ xss
represents the set of possible values of
the correspondent component.
Namely, suppose that xss
has the form
xs[1], xs[2], ... , xs[k]
Then cartesian
returns a list containing all possible lists of
the form
x[1] ∷ x[2] ∷ ... ∷ x[k] ∷ []
where x[i] ∈ xs[i]
. In Agda, this property of cartesian
is
formulated as follows:
∈*↔∈cartesian :
∀ {A : Set} {xs : List A} {yss : List (List A)} →
Pointwise.Rel _∈_ xs yss ↔ xs ∈ cartesian yss
A proof of the theorem ∈*↔∈cartesian
can be found in Util.agda
.
A functional specification of big-step multi-result supercompilation
is given in the form of a total function (in BigStepSc.agda
)
that takes the initial configuration c
and returns a list of residual
graphs:
naive-mrsc : (c : Conf) → List (Graph Conf)
naive-mrsc′ : ∀ (h : History) (b : Bar ↯ h) (c : Conf) →
List (Graph Conf)
naive-mrsc c = naive-mrsc′ [] bar[] c
naive-mrsc
is defined in terms of a more general function
naive-mrsc′
, which takes more arguments: a history h
,
a proof b
of the fact Bar ↯ h
, and a configuration c
.
Note that naive-mrsc
calls naive-mrsc′
with the empty history and
has to supply a proof of the fact Bar ↯ []
. But this proof is
supplied by the whistle!
naive-mrsc′ h b c with foldable? h c
... | yes f = [ back c ]
... | no ¬f with ↯? h
... | yes w = []
... | no ¬w with b
... | now bz with ¬w bz
... | ()
naive-mrsc′ h b c | no ¬f | no ¬w | later bs =
map (forth c)
(concat (map (cartesian ∘ map (naive-mrsc′ (c ∷ h) (bs c))) (c ⇉)))
The definition of naive-mrsc
is straightforward.
-
If
c
is foldable to the historyh
, a back-node is generated and the function terminates. -
Otherwise, if
↯ h
(i.e. the historyh
is dangerous), the function terminates producing no graphs. -
Otherwise,
h
is not dangerous, and the configurationc
can be decomposed. (Also there are some manipulations with the parameterb
that will be explained later.) -
Thus
c ⇉
returns a list of lists of configurations. The function considers each eachcs ∈ c ⇉
, and, for eachc′ ∈ cs
recursively calls itself in the following way:
naive-mrsc′ (c ∷ h) (bs c) c′
producing a list of residual graphsgs′
. So,cs
is transformed into gss, a list of lists of graphs. Note thatlength cs = length gss
. -
Then the function computes cartesian product
cartesian gss
, to produce a list of lists of graphs. Then the results corresponding to eachcs ∈ c ⇉
are concatenated byconcat
. -
At this moment the function has obtained a list of lists of graphs, and calls
map (forth c)
to turn each graph list into a forth-node.
The function naive-mrsc
is correct (sound and complete)
with respect to the relation _⊢MRSC_↪_
:
⊢MRSC↪⇔naive-mrsc :
{c : Conf} {g : Graph Conf} →
[] ⊢MRSC c ↪ g ⇔ g ∈ naive-mrsc c
A proof of this theorem can be found in BigStepScTheorems.agda
.
The problem with naive-mrsc′
is that in the recursive call
naive-mrsc′ (c ∷ h) (bs c) c′
the history grows (h
becomes c ∷ h
), and the configuration
is replaced with another configuration of unknown size (c
becomes
c′
). Hence, these parameters do not become "structurally smaller".
But Agda's termination checker still accepts this recursive call,
because the second parameter does become smaller (later bs
becomes bs c
). Note that the termination checker considers
bs
and bs c
to be of the same "size". Since bs
is smaller
than later bs
(a constructor is removed), and bs
and bs c
are
of the same size, bs c
is "smaller" than later bs
.
Thus purpose of the parameter b
is to persuade the termination
checker that the function terminates. If lazy-mrsc
is reimplemented
in a language in which the totality of functions is not checked,
the parameter b
is not required and can be removed.