4.3 Related Work
5.1.2 A pattern of Arrows for reinterpretation
Our goal is to adapt the generic Arrow framework to our circuits domain in such a way that we can give our descriptions multiple interpretations (§4.2.1). We aim to model our circuits as Arrows simply by replacing the function arrow (→) in the simple circuit EDSL shown in Figure4.1 on page75with suitable Arrows, uncurrying as necessary.
One might hope to adaptHughes’s original motivation for Arrows to the goal of reinterpretation, obtaining a netlist “statically” and a simulation semantics “dynamically”. In particularHughes gave an abstract interface to the parser combinators ofSwierstra and Duponcheel(1996) using the followingParserArrow:
dataStaticParsers = SP Bool[s]
newtypeDynamicParsersα β = DP((α, [s]) → (β, [s])) dataParsersα β =P(StaticParsers) (DynamicParsersα β)
wheresis the type of symbols. The idea is thatStaticParsersdescribes the preconditions for invokingDynamicParsersα β– that is, whether it accepts the empty string, and which tokens it accepts first. The generic Arrow plumbing described above can safely propagate this information in a way that Monadic operations cannot.
Unfortunately we cannot obtain a netlist using this approach, as the structure of an Arrow network is only partially “static”; we cannot analyse the Arrow plumbing, which is polymorphic and allows for arbitrary transformations on values using thearrcombinator. For example, as we cannot even determine what a functionf of type (α,α) → (α,α) does from within the language we have no way of knowing what the netlist forarrf should be.
With these observations in mind we evolve a typical Monadic type-class-based abstraction, followingBjesse et al.(1998);Erkök(2002);Matthews et al.(1998);Paterson(2003) amongst others, to an Arrow setting. This is our starting point:
typeBit= Bool
classMonadm ⇒ Circuitm sigwhere and2 :: (sigBit,sigBit) → m(sigBit)
neg :: sigBit → m(sigBit)
delay :: α → sigα → m(sigα)
Intuitively the class contains an adequate set of gates, with the Monadmproviding any effects we need to interpret the circuit, and the sig type constructor providing the temporal struc- ture. Specifically we can recover our simple simulation semantics of Figure4.1by takingm
to be the identity Monad with type constructorIdMα= αandsig to be our signal type, i.e.
sigα = Signalα. A netlist could use a state Monad: newtypeStateMsα = StateM(s → (α,s))
with the state providing a name supply for identifying the components, andsigαdefined to be such a name; a value “flowing on a wire” is the identity of the component that drives it.
Our first step is to replace the Monad with an Arrow:
classArrow( ) ⇒ Circuit( )where andA :: (Bit,Bit) Bit
notA :: Bit Bit
delayA :: α→ (α α)
We simultaneously eliminate thesigparameter by incorporating temporal behaviour into the Arrow, exploiting the “morally correct” synchronous isomorphism (§4.3.5):
(sigα,sigβ) 'sig(α,β)
Once again, a simulation Arrow might takeα βto be [α] → [β], and the netlist can use the Kleisli Arrowα → StateMsβ. Consequently we need to directly reinterpretBit, which motivates the following multi-parameter type class (MPTC):
classArrow( ) ⇒ Circuit( )bitwhere andA :: (bit,bit) bit
notA :: bit bit
delayA :: α→ (α α)
Unfortunately all uses ofdelayAare ambiguous as it does not constrain thebitparameter. A partial (and in our case, adequate) solution is to require that each of the members of the class must mention all of the type variables in the head of the class declaration. We note that this does not completely resolve this issue as there can still beread◦show-type ambiguities where a constrained type variable is not present in the type of the expression. SeeOdersky, Wadler, and Wehr(1995) for further discussion on this point.
Implicit in the first two definitions were the constructors of theBittype, which we need to make manifest for the abstract typebit. Therefore we arrive at these definitions:
classArrow( ) ⇒ Circuit( )bitwhere
falseA :: () bit
trueA :: () bit
andA :: (bit,bit) bit
notA :: bit bit
classArrow( ) ⇒ Delay( )where
delayA :: α→ (α α)
The key invariant we require of all instances of these classes is that there are no user-visible operations on the types used to instantiatebit that distinguish its values. For instance an
interpretation usingbit = Boolallows the pure Arrowarr(uncurry(&&)) to be silently mixed with uses ofandA, defeating our attempts at reinterpretation. Similarly the Arrow cannot be the bare function type constructor (→).
Provided interpretations and circuits respect this invariant pure Arrows can only have innocuous behaviour. We discuss this further in §5.6.
We note that these particular classes are not ideal to program with; for instance,delayAcannot be initialised at thebit type usingfalseAandtrueA! We discuss pragmatics in the following sections.
This style of reinterpretation has been named “finally tagless” byCarette et al.(2009); we mildly extend their work by allowing representations of types such asBitto vary with the interpretation. It can be seen as a partial solution to the expression problem as popularised by Wadler: this use of type classes gives us an “open” syntax in the sense that we can define more constructs simply by defining more classes, and interpret these constructs without disturbing the exist- ing instances. Some extensions (such as non-determinism and circuit probes) may involve significant renovation of the Arrow’s representation, however.