8.3
an abstract supercompiler in haskell
Now we can translate the abstract supercompiler from Chapter7into Haskell. Once again, we shall use Haskell’s type-classes extended with type families in place of Agda’s postulates.
8.3.1 Languages
A type-class instance, Language `, defines the data types and functions needed for a language (labelled `) with a small-step operational semantics. The fol- lowing class deceleration is a direct translation of the Section7.3of the Agda abstract model.
class Language ` where data Syntax `
data Value ` data State `
initState:: Syntax `→ State `
step:: State `→ Either (Value `) (State`)
In Figure8.5, we use instances of these signatures to derive a function run. We also define, for any Language `, Syntax `, Value ` and State ` instances for the Evaluable type-class. These definitions are the similar to the Agda model in Section7.3. However, given that our comparisons will be based on runtime results, we need to ensure that our evaluations terminate, even when the test- program is non-terminating. In Figure 8.5, we use the runFor combinator to limit our evaluations to a finite number of operational steps.
Appealing to these definitions of Evaluable and the proofs in Figure7.13, we claim that the initState and step functions preserve any correctness property, defining verified versions in Figure8.6. These claims are expressed using the trustMe method of introducing verified functions.
8.3.2 Normalisation
Any instance of Language ` can be extended with an isNormal function to form an instance of Normaliser `.
class Language `⇒ Normaliser ` where isNormal:: State `→ Bool
A generic, normalisation function normalisev is defined in Figure 8.7. It
follows the definition given in Section7.5.1but has been adjusted to use the cond_v combinator from Figure8.21.
-- Run from a given state until termination run:: Language `⇒ State ` → Value `
run=either id run◦ step
instance Language `⇒ Evaluable (Value `)where type Resulte (Value `) =Maybe(Value `)
evale =Just
-- Run a state for a finite number of steps
runFor:: Language `⇒ Int → State ` → Maybe(Value `)
runFor 0 s =Nothing
runFor n s =either Just (runFor (pred n))$ step s instance Language `⇒ Evaluable (State `)where
type Resulte (State`) =Maybe(Value `)
evale =runFor 1000
instance Language `⇒ Evaluable (Syntax `)where type Resulte (Syntax `) =Maybe(Value `)
evale =evale◦ initState
Figure 8.5:Definitions over Language instances.
initState_v :: Language `⇒ Syntax ` y State `
initState_v =trustMe "initState_v" $ const initState stepv :: Language `⇒ State ` y Either (Value `) (State `)
stepv =trustMe "step_v" $ const step
Figure 8.6:‘Trusted’ verified forms of initState and step.
normalisev:: Normaliser `⇒ State ` y Either (Value `) (State`)
normalisev =stepv >>> left_v||| cond_v (const isNormal)right_v normalisev Figure 8.7:Normalisation for instances of Normaliser .
8.3 an abstract supercompiler in haskell 8.3.3 Termination
Any language instance can also be extended with information about a super- compilation termination condition (Section5.3.2). As with the Agda abstract model in Section7.5.2, an instance must define a data type containing historical information, an empty history and a method for determining if the termination condition has been triggered.
class Language `⇒ Terminator ` where data History `
emptyHistory :: History `
canContinue :: History `→ State ` → Maybe(History `)
8.3.4 Memoisation
Memoisation is required to reuse the results within a supercompilation and is essential for supercompiling recursive programs, as was discussed in Sec- tion5.3.4. Another type-class determines the method by which a supercompiler instance memoises results. Each particular language instance provides data types to hold binding information, a verified function memoisev for memoising
supercompiler phases and a verified function residualisev that indicates a point
at which a new binder may be generated.
class Language `⇒ Memoiser ` where data BindingIn `
data BindingOut `
memoisev :: Phasev `→ Phasev `
residualisev :: Phasev `→ Phasev `
emptyIn :: BindingIn `
runBWriter :: BoundSyntax `→ Syntax `
data BWriter ` a=BWriter (BindingOut`)a type BoundSyntax ` =BWriter `(Syntax `)
type Phasev `= (BindingIn `, History `) State ` y BoundSyntax `
The runBWriter function is used to retrieve syntax in a binding context. We use it to define an evaluable type for such syntax. As runBWriter is shown to be correct-by-construction in Section7.5.3, we define a verified variant runBWriterv
8.3.5 Splitting
Splitting is the process of taking a state in a normal form and separating it into supercompilable subterms, as was described in Section5.3.3. An instance Splitter ` defines a Split ` data-structure that holds these subterms and a residual context, the method of combining the results of their supercompilation into a complete expression.
The verified function splitv turns intermediate states into splits while the
applySubtermsv applies a verified function to each subterm in the split before recombining the results into an expression. The function runSplit is used to recover a state from a split. An Evaluable instance is given in Figure8.9, using runSplit to define the semantics of a split.
class Language`⇒ Splitter ` where data Split `
splitv :: State ` y Split ` applySubtermsv :: Phasev `→
(BindingIn`, History `) Split ` y BoundSyntax ` runSplit :: Split ` → State `
8.3.6 Supercompiler
Finally a type-class Supercompiler ` depends on all the previous type-classes rep- resenting supercompiler components. In addition to these, it defines finalStatev
— a verified function for retrieving a state from a final value. class(Normaliser `, Terminator `, Memoiser `, Splitter `)⇒
Supercompiler ` where finalStatev:: Value ` y State `
A supercompiler is defined for instances of the Supercompiler type-class in Figure8.10. It follows the definitions in Section7.5.5but, once again, is adjusted for the ternary verified functions types.
8.3 an abstract supercompiler in haskell
instance Memoiser `⇒ Evaluable(BoundSyntax `)where type Resulte (BoundSyntax `) =Maybe (Value `)
evale =evale◦ runBWriter
runBWriterv :: Memoiser `⇒ BoundSyntax ` y Syntax ` runBWriterv =trustMe "runBWriter_v" $ const runBWriter
Figure 8.8:Evaluable types and verified functions for binding contexts.
instance Splitter `⇒ Evaluable(Split`)where type Resulte (Split `) =Maybe (Value `)
evale =evale◦ runSplit
Figure 8.9:Evaluable instances for splits.
supercompile:: Supercompiler `⇒ Syntax ` y Syntax ` supercompile =initState_v >>>
applyv drive (emptyIn, emptyHistory)>>> runBWriterv
drive:: Supercompiler `⇒ Phasev `
drive =memoisev drive0
drive0:: Supercompiler `⇒ Phasev `
drive0 =constv normalisev >>>(constv finalStatev >>> tie)||| (maybe_v canContinue0 tie drive)
where canContinue0 (r, h)x = (,)r <$>(canContinue h x)
tie:: Supercompiler `⇒ Phasev `
tie =constv splitv >>> applySubtermsv (residualisev drive)
-- Only ASTs that are well-typed and canonical. applyFilter:: ProgGen.ProR → Filtered (ProgGen.ProR)
applyFilter x =Filtered [ProgGen.good x]x instance Serial (Filtered ProgGen.ProR)where
series =applyFilter <$>^ series
seriesWithEnv =applyFilter <$>^ seriesWithEnv
Figure 8.11:Serial instance of filtered abstract syntax trees.