• No results found

Typechecking

In document Bidirectional Programming Languages (Page 81-85)

We were originally motivated to study quotient lenses by the need to work “modulo insigni - cant details” when writing lenses to transform real-world data formats. However, as we began using our language to build larger examples, we discovered a signi cant—and completely un- expected—side bene t: quotient lenses allow us to assign many bidirectional transformations

coarsertypes than the strict lens laws permit, easing some serious tractability concerns.

The need for precise types stems from a fundamental choice in our design: put functions

are total. Totality is attractive to users of lenses, because it guarantees that any valid view can be put back with any valid source. However, for exactly the same reason, totality makes it

dif cult to design lens primitives—theputfunction must do something reasonable with every

valid view and source, and the only way that a lens can avoid having to handle certain structures is by excluding them from its type. Thus, in practice, a lens language with a suf ciently rich collection of primitives needs to be equipped with a correspondingly rich algebra of types.

To be sure, working in a language with very precise types has many advantages. For ex- ample, Boomerang’s type checker, based on regular expressions, uncovered a subtle source of ambiguity in the UniProt ASCII format. But it also imposes burdens—programmers must write programs that satisfy a very picky type checker, and implementations must mechanize these precise analyses, often relying on algorithms that are computationally expensive. Fortunately, the increased exibility of quotient lenses and canonizers can be exploited to loosen types and alleviate these burdens. We discuss three examples of this phenomenon in this section.

The rst example involves thecolumnizetransformation, which was de ned as a primitive

canonizer in Section4.4. The mappings between long lines of text and blocks of well-wrapped

lines are a bijection, so they trivially satisfy the lens laws. We could de necolumnizeas a basic

lens—either as a primitive, or using combinators (although the combinator program would have to keep track of the number of characters on the current line, and so would be quite tedious to write). However, the type of this lens, whose view type is the set of minimally-split, well-wrapped blocks of text (i.e., sequences of lines that must be broken exactly at the margin column, or ones that must be broken at the column just before the margin because the next two characters are not spaces, or lines that must be broken at the second-to-last column..., and so on) is horribly complicated and cumbersome—both for programmers and in the implementation.

We could loosen the type to match the one we gave to thecolumnizecanonizer—i.e., to arbitrary blocks of text, including blocks containing “extra” newlines—but changing the type in this

way also requires changing the put function in order to avoid violating the GP law. In

particular, if we take a concrete block of text containing some extra newlines, map it to an

abstract line byget, and immediately map it back to a concrete block by put, then the strict

version of GP stipulates that all of the extra newlines must be restored exactly. Thus, the

putfunction cannot ignore its concrete argument and insert the minimal number of newlines

needed to avoid spilling over into the margin; it must also examine the concrete string and

restore any extra newlines from it. Formulatingcolumnizeas a canonizer rather than a lens,

avoids all of these complications and results in a primitive whose type and behavior are both simple.

The second example of a transformation whose type can be simpli ed using canonizers is

sort. As withcolumnize, it is possible to de ne a basic lens version ofsort. To sort(S1. . . Sk),

we form the union of lenses that recognize the concatenations of permutations of theSis and

apply the appropriate permutation to put them in sorted order. This lens has the behavior we

want, but its type on the concrete side is the set of all concatenations of permutations ofSis—a

type whose size grows as the factorial ofk! Representing this type in the implementation rapidly

becomes impractical. Fortunately, the combinatorial blowup can be avoided by widening the

concrete type to(S1|. . .|Sn). This type over approximates the set of strings that we actually

want to sort, but has an enormously more compact representation—one that grows linearly

withk. Of course, having widened the type in this way, we also need to extend the canonizer’s

functional components to handle this larger set of strings. In particular, we must extendcanonize

to handle the case where several or no substrings belong to a givenRi. A reasonable choice,

which works well for many examples is to simply discard the extras and ll in missing ones with defaults.

The nal example involves the duplication operator. As discussed above, it is possible to have duplication as a basic lens, but to satisfy PG the type of strings in the view must

include an equality constraint. By de ning dup1 and dup2 as a quotient lens, we obtain a

primitive with a much simpler—in fact, regular—type.

These examples show how quotient lenses ease certain aspects of typechecking. However, they complicate other aspects of typechecking because the typechecker needs to track equiva-

lence relations. In particular, the typing rules for left and right quotienting, sequential compo- sition, and union all place constraints on the equivalence relations mentioned in the types of

sublenses. For example, to check that the composition (l;k) is well typed, we need to verify

that the equivalence onl’s view and the one onk’s source are identical.

In the rest of this section, we describe two different approaches to implementing these rules. The rst uses a coarse analysis, simply classifying equivalences according to whether they are or are not the equality relation. Surprisingly, this very simple analysis captures our most common programming idioms and turns out to be suf cient for all of the applications we have built. The second approach is more re ned: it represents equivalence relations by rational functions that induce them. This works, in principle, for a large class of equivalence relations including most of our canonizers (except for those that do reordering). Unfortunately, it requires representing and deciding equivalences for nite state transducers, which appears too expensive to be useful in practice.

The rst type system is based on two simple observations: rst, most quotient lenses origi- nate as lifted basic lenses, and therefore have types whose equivalence relations are both equal- ity; second, equality is preserved by many of our combinators including all of the regular op-

erators,swap, sequential composition, and even (on the non-quotiented side) the left and right

quotient operators. These observations suggest a coarse classi cation of equivalence relations into two sorts:

τ ::= Identity | Any

We can now restrict the typing rules for our combinators to only allow sequential composition,

quotienting, and union of types whose equivalence relation type is Identity. Although this

restriction is draconian (it disallows many quotient lenses that are valid according to the typing rules presented in earlier sections), it turns out to be surprisingly successful in practice—we have not needed anything more to write many thousands of lines of demo applications. There are two reasons for this. First, it allows two quotient lenses to be composed, whenever the uses oflquotare all in the lens on the left and the uses ofrquoton the right, a very common case. And second, it allows arbitrary quotient (with any equivalences) to be concatenated as long as the result is not further composed, quotiented, or unioned—another very natural idiom. This is the typechecking algorithm used in Boomerang.

In theory, we can go further by replacing theIdentity sort with a tag carrying an arbitrary

nite state transduction f—i.e., a function computable by a nite state transducer (Berstel,

1979):

τ ::= Fst off | Any

Equivalence relations induced by rational functions are a large class that includes nearly all of the equivalence relations that can be formed using our combinators—everything except quo-

tient lenses constructed from canonizers based on sort and swap. Moreover, we can decide

equivalence for these relations.

4.5.1 De nition: Letf ∈A B be a rational function. Denote by∼f the relation{(x, y) A×A | f(x) =f(y)}.

4.5.2 Lemma: Letf ∈A B andg ∈A →C be rational and surjective functions. De ne a

rational relationh⊆C×B as(f◦g−1). Then(∼g ⊆ ∼f)iffhis functional.

Proof: Let us expand the the de nition ofh

h(c) ={f(a) | a∈Aandg(a) =c}

Observe that, by the surjectivity ofgwe haveh(c)̸=.

()Suppose that∼g ⊆ ∼f.

Let b, b′ h(c). Then by the de nition of h, there exista, a′ A withb = f(a) and

b′ =f(a′)andg(a) =c=g(a′). We have thata∼g a′, which implies thata∼f a′, and

sob=b′. Sincebandb′were arbitrary elements ofh(c), we conclude thathis functional.

()Suppose thathis functional.

Leta, a′ Awitha∼g a′. Then there existsc∈ Csuch that g(a) =g(a′) =c. By the

de nition ofh, and our assumption thathis functional, we have thatf(a) =h(c) =f(a′)

and soa∼f a′. Sinceaanda′were arbitrary, we conclude that∼g ⊆ ∼f.

Proof: Recall that rational relations are closed under composition and inverse. Observe that

∼f =∼giff bothf◦g−1andg◦f−1are functional. Since these are both rational relations, the

result follows using the decidability of functionality for rational relations (Blattner,1977).

The condition mentioned in union can also be decided using an elementary construction on rational functions. Thus, this ner system gives decidable type checking for a much larger set of quotient lenses. Unfortunately, the constructions involved seem quite expensive to implement.

In document Bidirectional Programming Languages (Page 81-85)

Related documents