-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathbody.tex
1461 lines (1299 loc) · 76 KB
/
body.tex
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
\section{Introduction}
% \dpw{
% I wrote some stuff but then realized that the \emph{real} story
% might go like this:
% \begin{itemize}
% \item Writing code takes a long time and is THE bottleneck for getting
% just about anything done. (Broader Impact!)
% \item Program synthesis to the rescue
% \item The old approach was to use theorem proving but it died.
% \item The new approach is to use examples and SMT and it has had
% all kinds of academic and commercial success. But it is fundamentally
% a first-order process.
% \item This proposal is all about bring the old and the new approaches together
% to get the best of both worlds. This is made possible by a key observation
% about how type theory can be used to guide the processing of examples
% by high-order programs.
% \end{itemize}
% We could fix up the intro to make it a bit more like the above later if we
% want. The current writing misses that story slightly.
% }
% It takes a long time to write code:
One of the great bottlenecks in many aspects of scientific discovery,
in economic development, in the functioning of our government, and
even in completing repetitive, personal, day-to-day tasks is the rate
at which we can write programs that automate our work. For instance,
a recent survey at Princeton University~\cite{computation-survey}
indicated that for those researchers who use computation in their
research, 35\% of their time was spent on coding. Such researchers
included Psychologists, Neuroscientists, Biologists, Chemists,
Sociologists and others --- 20 different disciplines in total.
If we want to accelerate the pace of science, we need
to find ways to accelerate the rate at which these scientists produce
reliable code, so they can spend more of their time doing the science
--- thinking about crafting experiments and interpreting their
results --- and less of their time on the more mundane elements of
writing, testing and debugging their code. Of course, the same is
true of much industry, government and our personal lives.
% Program synthesis can improve programmer productivity
One way to accelerate the productivity of scientists, or just about
anyone else who uses computation on a regular basis, is to tackle the
grand computer science challenge of \emph{program synthesis}: Given a
specification of a computational problem, find a program that will
realize that specification.
Though the problem synthesis problem is
old, with roots tracing back at least as far as Green's work in the
60s~\cite{green-ijcai-1969} and Manna's work in the late 70s and
80s~\cite{manna-tse-1979,manna-plas-1980}, there has been a recent
resurgence and return to this problem over the last several years.
For example, Gulwani~\cite{gulwani-popl-2011} has demonstrated that
many spreadsheet data transformations can be phrased as synthesis
problems and solved efficiently when a user gives just a handful of
examples. Others have developed code completion algorithms that help
programmers manage large APIs~\cite{perelman-pldi-2012,gvero-pldi-2013},
implemented cache-coherence protocols~\cite{udupa-pldi-2013}, and developed
algorithms for data extraction from web pages~\cite{vu-pldi-2014}.
Such recent success stories demonstrate the potential for program
synthesis to have a transformative impact on the lives of programmers
as well as everyday people who do not yet consider themselves
programmers.
% Why program synthesis has been successful recently
%% In general, these recent successes have come in part from the
%% tremendous progress on algorithms for key
%% decision procedures over the last several decades, particularly those
%% for SAT and SMT problems, as a common synthesis technique is to
%% convert specifications into logical representations and then to apply
%% the appropriate decision procedures. There are many instances of
%% this approach in the
%% literature~\cite{solar-lezama-pldi-2005,solar-lezama-pldi-2007,solar-lezama-pldi-2008,bodik-popl-2010,gulwani-pldi-2011}.
One of the main sources of progress in synthesis algorithms
has come from effective use of collections of
\emph{examples} as specifications. In many domains, examples are
readily available and require little ``work'' by the programmer to
assemble. For instance, in spreadsheets, there is data to form the
basis for examples everywhere, and hence the success, both
academically and industrially, of Gulwani's work on
FlashFill~\cite{gulwani-popl-2011}, which synthesizes spreadsheet data
transforms efficiently.
Yet, even with increasing industrial and academic attention devoted
to example-directed synthesis, key theoretical and practical questions remain
unanswered: What exactly are these ``examples?''
How do we understand their semantics? How do they interact with other
kinds of specifications? Can richer specifications, together with
examples, lead to a new generation of synthesis algorithms?
% Explain that it does not cover all the bases yet.
%% Despite these successes, SAT and SMT-based synthesis is
%% primarily a \emph{first-order} process. At its core,
%% SAT provides us with a means to synthesize (first-order)
%% bits and SMT provides us with a means
%% to work with more complex (first-order) data structures. Of course,
%% modern software systems, with reuseable, parameterized libraries, call-backs and
%% design patterns are fundamentally \emph{higher-order}. If we are to
%% synthesize useful programs in this higher-order
%% space, we need new ways of thinking about the synthesis problem and
%% powerful new algorithms to reduce the problem higher-order synthesis
%% back to the first-order world where efficient decision procedures exist.
% Type theory will cover all the bases.
We believe that \emph{type theory} can help us answer these questions and will lead to
new program synthesis frameworks that are both \emph{more efficient} and
\emph{more expressive} than ever before.
%Types are, of course, the most prevalent
%light-weight formal method, showing up in simple forms in old languages (C, Java, \emph{etc.})
%and in increasingly sophisticated ways in more modern languages (Rust, Haskell).
%As such
Our confidence in type theory
as a unifying framework for example-directed program synthesis is bolstered
by several key observations:
\begin{enumerate}
\item There are orders of magnitude fewer typed programs than
untyped programs. There are an even smaller number of typed programs in
normal form.
\item Type theory explains how to decompose the complex
\emph{examples} and \emph{expressions} in to simpler,
independent examples and expressions, thereby reducing complex synthesis
problems to smaller, simpler, more tractable and better-understood
problems.
\item Type theory shares a connection to intuitionistic logic via the
Curry-Howard Isomorphism, meaning advances in theorem proving over the
decades can now be exploited to yield advances in program synthesis.
\item Advances in type systems over the past several decades have made it
possible not only to specify the pure, functional behavior of
programs with varying degrees of specificity, but also their effects.
\end{enumerate}
The first observation is broadly known, especially in the context of
theorem proving in intuitionistic logics. For instance, the
proof-theoretic idea of searching only for well-typed programs in
$\beta$-\textit{normal}, $\eta$-\textit{long} form has been used by
Byrnes~\etal~\cite{byrnes-1999} and also by
Gvero~\etal~\cite{gvero-pldi-2013}). Nevertheless it has
important pragmatic consequences when implementing program synthesis.
In particular, we propose, in part, to use synthesis techniques that
enumerate possible programs and then to check whether the generated programs
satisfy the given specification. The degree to which we can scale
such synthesis procedures is directly related to the number of
programs that we need to enumerate; using the structure afforded by
known results in the literature on type theory appears to be an
extremely effective means of limiting the set of programs to
enumerate. To investigate this point empirically, we performed an
experiment that enumerated all of the untyped programs of a given size
(with a given number of abstract syntax tree nodes) and compared that
to the number typed programs of type \cd{nat -> nat} in normal form of a given size.
Figure~\ref{fig:counting} presents the results, which show that the
number of well-typed terms is orders of magnitude smaller. Such a graph
shows the results of simple typing; more advanced type theories will
cut down the search space further.
\begin{figure}[t]
\begin{center}
\includegraphics[width=4in]{enumeration-cropped.pdf}
\end{center}
\caption{Comparing the number of typed and untyped terms (log scale). }
\label{fig:counting}
\end{figure}
The second observation is a novel contribution of PI Zdancewic's recent work~\cite{OZ15}
and, in our view, holds
extreme promise for the construction of a new significant class of
program synthesis algorithms based on the principles of type theory.
In more detail, when checking that a function
$\lambda x.e$ has type $\tau_1 \rightarrow \tau_2$, the standard algorithm will
hypothesize that $x$ has type $\tau_1$ and proceed with the simpler
problem of checking that expression $e$ has type $\tau_2$. This is the
basic process by which a type checker decomposes the complex task of
checking (higher-order) functions in to the simpler task of checking
(first-order) expressions. Analogously, when synthesizing a program
from a collection of input-output examples $\mathit{inputs}
\Rightarrow \mathit{outputs}$, an algorithm might hypothesize the
generated (possibly higher-order) program will see $\mathit{inputs}$
associated with some argument $x$. Then, given such a hypothesis,
that algorithm might work on the simpler problem of synthesizing an
expression that produces $\mathit{outputs}$.
A similar reduction
in complexity occurs when considering many other type constructors
as well --- not just functions. For instance, we can type check the elements of a pair
$(e_1, e_2)$ with type
$\tau_1 * \tau_2$ by \emph{independently} checking that $e_1$ has
type $\tau_1$ and $e_2$ has type $\tau_2$. Likewise, we can
synthesize a program with type $\tau_1 * \tau_2$ with examples
$(\mathit{examples_1}, \mathit{examples_2})$ by \emph{independently}
synthesizing the left-hand side using $\mathit{examples_1}$ and
the right-hand side using $\mathit{examples_2}$. Generating
$m$ possible programs for left-hand side and checking them
independently of
the $n$ possibilities for the right-hand side has a cost proportional to
$m + n$ whereas generating all pairs and checking both sides
simultaneously has a cost proportional to $m * n$. Hence, the structural
decomposition suggested by type theory has the potential for enormous
performance gains over any naa\"{i}ve strategy. In general, much is known about
the structure of typed programs (often via
the Curry-Howard isomorphism, from the structure of
intuitionistic proofs) and it would appear that we can exploit
this structure to simplify and decompose examples
and thereby program synthesis problems.
The upshot of this new insight is that it is possible to modify the
typing rules of a programming language so that they ``push'' examples
towards the leaves of the typing derivation trees that serve as the
scaffolding for the generated program terms. Doing so permits the
algorithm to evaluate candidate terms \textit{early} in the search
process, thereby potentially pruning the search space early. Rather
than following the na\"{i}ve strategy of ``enumerating and
\textit{then} evaluating,'' our algorithm follows the more nuanced
approach of ``evaluating \textit{during} enumeration.''
So far, Observations 1 and 2 appear to be extremely robust both in theory and in
practice. Indeed, in preparation for this grant proposal, we have
developed a prototype synthesis algorithm based on these two
observations~\cite{OZ15}.
To date, the algorithm has only been designed to handle
simple recursive, algebraic data types but we have tested it on over 40
examples and found it to (a) synthesize both higher-order and
first-order programs equally well, and (b) be competitive with
existing state-of-the-art synthesis algorithms on the first-order
examples.
Observations 3 and 4 hold great promise for the future. Because of the
correspondence between between type theory and intuitionistic logic,
we can dig in to the extensive literature on theorem proving for
intuitionistic logics~\cite{handbook-automated-reasoning} for new
kinds of algorithms that may be applied to the problem of program
synthesis. For example, rather than search for natural deduction
proofs/programs, we can explore the use of sequent calculus-style
proofs, and we can do so either using backwards or forward search.
The literature also provides ideas for context management, program or
proof representations, and efficient data structures. Even more
exciting, the literature on type systems has grown enormously over the
past several decades. Types are now able to describe complex
phenomena such as strings and xml schema via regular expression types,
or complex (abstract syntax and other) trees via GADTs and refinement
types, as well as transformations between these objects. Types are no
longer limited to descriptions of pure functions --- they can describe
computational effects as well. Monads separate the pure from the
impure and can be used to describe simple effects such as exceptional
behavior. Linear, affine, relevant and ordered type theories describe
the various ways in which resources may be used, providing the
possibility of rich specifications for allocating, using, updating or
consuming resources according to various protocols. Type and effect
systems have also been used to describe memory management and to
control concurrent programs.
As mentioned above, the approach of using types and techniques from
proof search to guide program synthesis has its roots in the seminal
work by Green~\cite{green-ijcai-1969} and
Manna~\cite{manna-tse-1979,manna-plas-1980}. What has changed in the
past forty years is three-fold: we now better understand how to
structure rich type systems and their corresponding proof theories, we
have seen how examples fruitfully enrich specifications and prune the
search space, and we now have vastly more computational resources at
our disposal.
We are not alone in exploring applications of type theory in program
synthesis---see the work by Kuncak
\etal~\cite{kuncak-pldi-2010,gvero-pldi-2013} for one approach;
Perelman \etal{} have also used types for searching through
libraries~\cite{perelman-pldi-2012}, and Feser \etal{} have looked at
using typed combinators for synthesis~\cite{Feser:2015}. However, to
date, there has been no systematic study of the wide variety of modern
type systems (e.g. GADTs, refinements, monadic, linear types) and how
they might be used together with examples to form the basis for an
expressive program synthesis platform.
There is also a large body of previous research on using other
techniques, such as program sketching and constraint solvers---see the
work by Solar-Lezama \etal{}
\cite{solar-lezama-thesis-2008,solar-lezama-pldi-2008,solar-lezama-pldi-2007,solar-lezama-pldi-2005}
---or computer-aided refinement of specifications---see the KIDS and
Specware
projects~\cite{smith2008generating,smith1996toward,smith1999mechanizing},
for instance. Our type-theoretic approach is largely complementary to
these, using different algorithms, different data structures, and
different forms of specification, though we expect to incorporate
their ideas where relevant.
%Refinement: KIDS / Specware
% \dpw{We should
% mention some early type-theoretic work and cite things like swarat's work ---
% we need to defend against the reviewer who knows that there has been
% some past work on type-theoretic synthesis.}
%To drive our research on these foundational topics and evaluate our
%algorithms and data structures,
%we plan to study the domain of systems configuration management.
%Unix system and application configuration is often performed by
%manually editing
% \dpw{Say something about the latter two observations? Yet another
% ``observation'' is that types are the most widely used lightweight formal
% method so they are a good means for people to specify stuff.}\saz{I
% think what you added is fine.}
%% More precisely, type theory has always been a powerful means of
%% reasoning about higher-order programs because it is compositional:
%% we can type check the elements of a pair $(e_1, e_2)$ with type
%% $\tau_1 * \tau_2$ by \emph{independently} checking that $e_1$ has
%% type $\tau_1$ and $e_2$ has type $\tau_2$. Likewise, we can type
%% check a function (by generating hypotheses about its argument)
%% independently of any of its uses. This compositionality allows
%% type checking to scale and programmers to work on separate modules
%% independently. By applying similar principles to the problem of
%% program synthesis from examples, it becomes clear that if required
%% to synthesize a program with pair type $\tau_1 * \tau_2$, we can
%% instead synthesize two programs separately, one for the left-hand
%% component of the pair with type $\tau_1$, and one for the
%% right-hand component of the pair with type $\tau_2$. Likewise, to
%% synthesis a program with function type $\tau_1 \rightarrow \tau_2$,
%% we can associate \emph{hypothetical examples} with the argument
%% $\tau_1$ and proceed to synthesize a program with type type
%% $\tau_2$. As we decompose the structure of the program we must
%% synthesize according to its type, we also decompose the structure
%% of the examples that serve as specifications. Eventually, given
%% arbitrary higher types and examples that match,
%% To understand why, observe first that program synthesis can, naively,
%% be implemented as a
%% simple-minded kind of search that enumerates one program after another
%% and as each program is generated, checks to see whether it satisfies
%% the given specification. If you enumerate enough programs, you will eventually
%% run in to one that satisfies your specification, if such a program
%% exists. Of course, there are many ways to enumerate programs, but Occam's Razor suggests
%% that simple programs are more likely to be the correct ones and hence an
%% enumeration that runs from the smallest to the largest programs is a reasonable
%% starting point.
%% Of course, the enumeration can take a very long time indeed. Superficially,
%% it is clear that type systems can play a role here because
%% they dramatically cut down the number of legal programs --- a large
%% number of syntactically legal programs do not type check and can be
%% rejected immediately, without wasting time determining whether they
%% satisfy the expected examples. Figure~\cite{fig:progs} presents a simple
%% experiment we ran, counting the number of typed and untyped terms, presented
%% as a log scale. The savings for using typed terms is enormous.
%% Still,
\paragraph*{Intellectual Merits.}
In summary, we propose a new research program on \emph{type-theoretic synthesis}
that will tackle the
problem of how to synthesize pure and effectful
programs in their full generality. Our proposed
research will simultaneously exploit both the structure of advanced
type systems \emph{and} the power of examples in program synthesis.
Our central research agenda will consist of four main thrusts:
\begin{itemize}
\item {\bf Core algorithms for type-theoretic synthesis.}
We will pursue fundamental research on core algorithms
and data structures necessary for type-theoretic synthesis.
Our goal is to push past current example-directed synthesis limits
by using new data structures and algorithms drawn, in part, from the
theorem proving literature.
\item {\bf Synthesis with advanced type theories.}
We will expand the expressive power of current synthesis systems by combining
specification via examples with specification in modern type theories.
In particular, we will study the use of various kinds of
refinement and dependent types to improve the specification of pure
programs, as well as the use of monads, linear types and effects
to specify effectful computations.
\item {\bf Theory.}
We will investigate the meta-theoretic properties of systems including
their semantics, soundness and completeness.
%A stretch goal is to begin
%to understand what kinds of examples
%are \emph{necessary} in order to synthesize certain classes of programs.
\item {\bf Evaluation and Applications.}
Unix application and systems configuration is a messy, error-prone task:
There are countless configuration formats, all with their own idiosyncratic
formats. We will evaluate the effectiveness of our foundational reseach
by applying it to the concrete problem
of synthesizing custom interfaces for managing, editing, and updating these
configurations. The programs we generate will be synthesized in the Augeas domain-specific
language for configuration management~\cite{augeas}, developed by Redhat,
and inspired by the Boomerang language~\cite{Boomerang07,bohannon2008boomerang,foster-thesis} for construction of bi-directional
programs.
% A special sub-domain of interest lies in the management of
% router configurations---such configurations are the sources of many
% network problems
% and their management is extremely
% challenging~\cite{feamster2005detecting,fogel+:network-config-analysis}. PI Walker has experience in this
% domain due to his recent work on the Frenetic project~\cite{frenetic},
% which involves programming and managing networks.
\end{itemize}
%% will involve developing new data structures
%% and algorithms to improve the scaling properties of our prototype
%% and to investigate how to incorporate a broad range of more advanced
%% type theories in to our synthesis algorithms. These new type theories
%% will include the following.
%% \begin{enumerate}
%% \item \emph{polymorphic types} to learn more general functions,
%% \item \emph{dependent types} to further constrain functions where specification
%% via examples is inefficient,
%% \item \emph{monadic types} to encapsulate simple effects such as exceptions,
%% \item \emph{linear types} (to encapsulate resource utilization effects such as
%% memory allocation), and
%% \item \emph{advanced type-and-effect systems} to encapsulate more general
%% kinds of effect specifications.
%% In order to evaluate our ideas in practice, we propose to develop a new
%% general-purpose, higher-order, effectful and typed programming language,
%% called \OSynth{}, based around OCaml, which incorporates synthesis
%% features in to the language.
%% \end{enumerate}
\paragraph{Broader Impacts.} In the long term, there are many potential
applications of program synthesis --- it is a generally applicable
technology that can potentially benefit any area of science or
industry in which non-programming experts must write code. Combining
type-theoretic synthesis techniques with other approaches has the
potential to significantly extend the scope of program synthesis
applications. In the short term, developing tools to synthesize new
interfaces for systems configuration will make it easier to manage
such systems and help to address
the daunting ``UNIX configuration nightmare''~\cite{unix-config-nightmare}.
In addition, program synthesis has many
educational uses: it can be exploited to give students feedback about
programming errors, or to generate novel examples or problems for them
to work with.
We also plan on having broad impact more directly through our educational plan.
At the undergraduate level, we plan to develop a series of undergraduate
research projects revolving around type systems, theorem proving and sythesis.
We hope that some of those projects will result in educational tools
that can be used in our undergraduate classes on functional programming.
At the graduate level, the type-theoretic approach to program synthesis
offers a compelling pedagogical framework that touches on many modern
ideas from programming languages; that structure will be applied in
the classroom to provide course material and projects.
The PIs have a history of engaging under-represented minorities in
their research projects and will continue to seek out opportunities to do so.
To cite just two concrete examples, PI Zdancewic recently graduated a Filipino
student with a PhD in program synthesis, and, earlier in his career, PI Walker
mentored an African American undergraduate student who won the CRA outstanding
undergraduate award, has gone on to get his PhD in computer science, and is
now faculty at Stanford. Both PIs have supervised women doctoral students.
\paragraph*{Proposal Structure.} In the following section, we further
explain our overall approach to program synthesis.
In Section~\ref{sec:research}, we flesh out the major components of our
research agenda further.
Section \ref{sec:impact} describes broader impacts,
Section \ref{sec:prior-support} describes our results from
prior NSF grants and Section \ref{sec:timeline} presents a
collaboration plan and research timeline.
\section{Program Synthesis with Simple Types and Examples}
\label{sec:prior}
Consider a core ML-like language featuring algebraic data types,
\cd{match}, top-level function definitions, and explicitly recursive
functions. A \emph{type-theoretic synthesis problem} is defined by:
(1) the data type
definitions and top-level let-bindings, (2) a goal type, and (3) a
collection of examples of the goal type. The synthesis task is to
find a program of the goal type that is consistent with the examples.
Figure~\ref{fig:stutter} presents a simple example of such a synthesis
problem, as well as an expected result.
To obtain such a result, our type-directed synthesis algorithm
performs two major operations: \emph{refining}
the constraints, the goal type and examples, and \emph{guessing} a
term of the goal type that is consistent with the example values.
\paragraph{Term guessing by enumeration.}
Guessing amounts to systematic enumeration of candidate terms in
increasing size, followed by evaluation in context to see whether they
agree with the current set of goal examples. Because we are operating
in a statically-typed setting, we avoid enumerating ill-typed terms,
which rules out nonsensical expressions like \cd{Nil Cons} (of which
there are exponentially many for a fixed term size). However, there are
still many well-typed terms that we also wish to avoid. For example, a term
that matches against a constant constructor, like \cd{match Nil with
Nil -> e1 | Const -> e2} is equivalent to a smaller term that has no
\cd{match} (in this case \cd{e1}). And to make matters worse, there
are an infinite number of these bad terms, obtainable by introducing
additional lambdas and applications. To avoid these sorts of
redundant terms, guessing only generates $\beta$-\emph{normal forms}:
terms that can be reduced no further.
\paragraph{Refinement.} The process of using type and example
constraints to guide the search process is refinement. As an example,
consider searching for solutions to the \cd{stutter} example of
Figure~\ref{fig:stutter}. Already from the goal type \cd{list ->
list}, we know\footnote{To rule out generation of many redundant expressions, we
generate terms in \emph{eta-long} beta-normal form,
which mandates that terms of type $\tau_1 \rightarrow \tau_2$ be
function definitions as opposed to other expressions that compute
functions.} that the top-level structure must be of the form
below.
\begin{center}
\cd{let rec f1 (l1:list) : list = ? }
\end{center}
\noindent When synthesizing the body of
the function, the examples tell us that in the ``world'' (\ie\ a
hypothetical evaluation of \cd{stutter}) where \cd{l1} is \cd{[]} the
expected answer is \cd{[]} and, in similarly in the world where
\cd{l1} is \cd{[0]} the expected answer is \cd{[0;0]}. So each such
input--output pair becomes a new example ``world'' where the output
value is the goal and \cd{l1} is bound to the corresponding input
value.
To fill in the body of the function, which must be a term of type
\cd{list}, we observe that no single list constructor agrees with the
examples (since there are examples starting with both \cd{Nil} and
\cd{Cons}). Hence guessing either \cd{Nil} or
\cd{Cons} is contradicted by examples.
We also try guessing terms that involve variables:
the smallest well-typed such term in this context is \cd{l1}, but that also
does not agree with the examples. The larger terms all involve
\cd{stutter l1}, which is well-typed, but not structurally recursive, so it
is also discarded.
With these possibilities exhausted, we next consider introducing a
\cd{match} expression. We first guess possible scrutinees to
\cd{match} against: they must have algebraic types and be terms
constructed from variables in the context. The only such term in this
case is \cd{l1}, so we consider refining the body of the function to:
\begin{figure}[t]
\begin{minipage}[t]{3.5in}
{\small
\begin{lstlisting}
(* Type signature for natural numbers and lists *)
type nat = type list =
| O | Nil
| S of nat | Cons of nat * list
(* Goal type refined by input/output examples *)
let stutter : list -> list |>
{ [] => []
| [0] => [0;0]
| [1;0] => [1;1;0;0]
} = ?
\end{lstlisting}
}
\end{minipage}
\quad
\begin{minipage}[t]{3in}
{\small
\begin{lstlisting}
(* Output:
* synthesized implementation of stutter *)
let stutter : list -> list =
let rec f1 (l1:list) : list =
match l1 with
| Nil -> l1
| Cons(n1, l2) ->
Cons(n1, Cons(n1, f1 l2))
in f1
\end{lstlisting}
}
\end{minipage}
\caption{An example program synthesis problem and the resulting
synthesized implementation. }
\label{fig:stutter}
\vspace{-3ex}
\end{figure}
\begin{center}
\cd{match l1 with Nil -> ? | Cons(n1, l2) -> ?}
\end{center}
\noindent When pattern matching, we distribute the evidence
according to how the \cd{match} evaluates in each example world.
In this case, \cd{l1} evaluates to \cd{Nil} in the first world and
\cd{Cons(_,_)} in the other two worlds. Therefore, we
send the first example to the \cd{Nil} branch and other
two examples to the \cd{Cons} branch. We are left with two
sub-goals in refined worlds. The \cd{Nil} case follows immediately:
the only example left in the goal is \cd{Nil} and there are two ways
to generate it: the constant \cd{Nil} constructor or \cd{l1}.
\paragraph{Recursive Functions.}
The \cd{Cons} case proceeds with another round of guessing, but the
solution requires a recursive call to \cd{f1}. How might we generate
a recursive function in this type-directed, example-based style? The
answer is that when introducing a recursive function like \cd{f1}, the
input-output examples for the function itself, interpreted as a
partial function, serve as a reasonable approximation of its behavior.
Here, the example for \cd{f1} in all three worlds would be the partial
function given by all three examples. Given this information in the
context, the guessing procedure can quickly determine that
\cd{Cons(n1, Cons(n1, f1 l2))} is a solution for the \cd{Cons} branch
of the match.
\paragraph{Formal System.}
The algorithm we have just described informally can be described formally
via a collection 3 mutually recursive judgements:
\begin{itemize}
\item $\Gamma \vdash \tau \ \& \ e \leadsto I$:
A judgement directed by the syntax of a goal type $\tau$ and a
collection of examples $e$, to synthesize
an introduction form $I$, such as a function, a pair or a constructor.
\item $\Gamma \vdash \tau \leadsto I$: A judgement to guess an introduction
form of type $\tau$ by enumerating all possible well-typed introduction forms.
\item $\Gamma \vdash \tau \leadsto E$: A judgement to guess an elimination
form (ie: a variable $x$ from the context, or a function application or
a projection) by enumerating all possible elimination forms of a type.
\end{itemize}
\paragraph{Results so far.} Figure~\ref{fig:maindata} presents a
selection of the synthesis results we have obtained so far, focusing
on the case for simple lists. For each entry of the table we verified
by manual inspection that the resulting synthesized program was a
correct implementation of the desired outcome. Synthesis time appears
competitive with other approaches, the most closely related of which
is the Escher system, by Albarghouthi~\etal~\cite{albarghouthi-cav-2013}. Yet, unlike those
other approaches, we are able to synthesize functions with
higher-order types such as map and fold just as easily as first-order
types.
\input{fig.maindata}
\section{Research Agenda}
\label{sec:research}
In this section, we discuss our core research proposal.
\subsection{Core Algorithms for Type-theoretic Synthesis}
\label{sec:core}
Fundamentally, efficient program synthesis requires algorithms
that can search the space of possible programs rapidly.
Our prototype system incorporates several key ideas that
help cut down this search space: We use \emph{examples} in addition
to types throughout the synthesis process;
we search only for \emph{eta-long}, \emph{beta-normal} forms;
we build efficient data structures called
\emph{refinement trees} to avoid enumerating
the same programs more than once; and we carefully manage the type-checking
context. Still, we plan to research a number of additional ways to
improve the search process.
\paragraph*{Beyond eta-long, beta-normal Form.}
By enumerating only the functions in eta-long, beta-normal form, we avoid
constructing or testing many programs. A key question is how
to cut down the search space even further --- there are still many programs
in eta-long, beta-normal form that are semantically equivalent.
One possibility is to bring stronger equational theories, such as the theory
of rings, groups, lists or arithmetic, in to the search process. For example,
we plan to investigate variations of our search process that incorporates
congruence closure algorithms~\cite{shostak:congruence,nelson-oppen,sjoberg:congruence} for
computing term equivalence. We hope to find a way to
sample just one representative from each equivalence class of terms
rather than many. However, we anticipate there will be a trade-off
between the cost of computing closures and the benefits gained from using
them. More research is required to determine the nature of this tradeoff.
\paragraph*{Search order variations.}
Our prototype synthesis algorithms are inspired by top-down natural
deduction proof strategies. However, as the range of type constructors
we consider expand, there are some search-order variations we can consider.
For instance, even adding ordinary tuples (pair types) introduces a new
set of interesting choices: tuple components can be extracted once and
eagerly, as soon as possible, or lazily on demand. It is unclear which
will perform better. When it comes to more sophisticated type constructors,
especially polymorphic types, further choices will likely reveal
themselves (see also the discussion of polymorphism in
Section \ref{sec:types} below).
In addition, entirely different search-orders that combine some bottom-up proof search in addition to our general top-down approach may perform well
in certain circumstances. We plan to evaluate such possibilities.
\paragraph*{Reducing example requirements.}
One of the weaknesses of our current approach is that when our recursive
divide-and-conquer algorithm considers how to synthesize different
branches of a match statement, it does so independently. This is
both a blessing and a curse: independence limits the combinatorial
explosion, but it also means that we can't reuse the parts of a function
that we have already synthesized. As a consequence, we sometimes need
extra examples, particularly when synthesizing recursive functions.
We plan to develop new techniques (which we call \emph{recursive back-patching
algorithms}) that will allow us to use portions of a function we have
already synthesized instead of requiring additional examples.
\paragraph*{Incorporating negative examples.}
Our algorithms so far use \emph{positive examples}. For instance,
the example \texttt{\{0 => 1\}} states the function we desire transforms
\texttt{0} in to \texttt{1}. However, users can also give \emph{negative
examples} that state undesirable behavior. For instance, Le and Gulwani~\cite{vu-pldi-2014} developed a user interface for data extraction
that allows users to select ``good'' (positive) examples of text
they would like to extract from a file and as well as ``bad'' (negative)
examples of text they do not want to select. The combination of good
and bad information helps refine the extraction algorithm. More generally,
there are some classes of functions, such as regular expressions,
which can be learned with both positive and negative examples
but cannot be learned with only positive examples~\cite{gold:inference}.
We will
investigate how to incorporate negative examples in to our
work. Whereas our current algorithms are linked, via the Curry-Howard
isomorphism, to proof search
in constructive logics, we may find a link to classical logics when
we begin to study the consequences of having negative examples.
More specifically, we will investigate the notion of an example
of a \emph{contradiction} (the key judgemental notion that
arises~\cite{pfenning:classical})
in an attempt to get to the heart of how learning
from examples interacts with classical logic.
\paragraph*{Data structure optimization.}
Our current prototype includes little work optimizing the basic data
structures or the evaluator we use. Throughout the
course of the grant, we will look for ways to optimize the core enumeration
and evaluation algorithms.
\subsection{Beyond Simple Type Systems}
\label{sec:types}
Our prototype currently operates over simple, recursive algebraic
data types. We intend to enrich the type theory of our synthesis
system in several different directions.
\paragraph*{Polymorphic Types.}
The first natural major extension to our system is to add
parametric polymorphism, which will allow us to synthesize generic
functions. Some of the standard OCaml collection libraries, such as
the List module, will serve as good benchmarks to evaluate our success
here. There will be several challenges involved in synthesizing
polymorphic programs. First, since our core algorithms are directed by
the desired type of our resulting expression, when we synthesize
that expression, we may need to search for polymorphic functions, which
when instantiated with particular type arguments, can generate results
of the expected type.
We anticipate incorporating unification of goal types in to our search
process to achieve such results. Second, thanks to Reynolds' parametricy
theorem~\cite{reynolds:parametricity} and Wadler's work on ``theorems for free''~\cite{wadler:theorems-for-free}, it is well known that there are simply
``not very many'' functions of certain polymorphic types. For instance,
the only functions with type $\forall \alpha. \alpha \rightarrow \alpha$
are the identity function and the function that does not terminate on
any input. When searching for polymorphic functions, we should be able
to exploit this fact to dramatically cut down the search space, especially
if we are given effective examples. On the latter point,
Voigtl\"{a}nder~\cite{voigtlander:bidirectionalization-for-free}
shows how to construct examples in a particular way, and then to exploit
parametricity to infer
the inverse of a given function. We will examine his methods to determine
whether similar concepts can be used in a more general synthesis setting.
Finally, we are interested in examining whether we can use polymorphism
to help us vary the synthesis search order in effective ways.
For instance, even if the type of program we are looking for, say
$\mathit{int} \rightarrow \mathit{int}$, is not polymorphic, we might
begin the search by looking for an abstraction of that function, perhaps
$\alpha \rightarrow \alpha$, for which (by parametricity) there are fewer
exemplars. If that fails, we might look for the more concrete
functions $\mathit{int} \rightarrow \mathit{int}$. Alternately,
when looking for a function with type $\mathit{int} \rightarrow \mathit{int}$,
we might actively \emph{avoid} looking for functions that can also have type
$\forall\alpha.\alpha \rightarrow \alpha$ under the assumption that if the
user wanted
a polymorphic function, they would have supplied a more general type.
Either way, we hope that polymorphic typing can be used in new ways
to help guide the search for programs that satisfy a specification.
\paragraph*{Generalized Algebraic Datatypes}
A second natural extension, once polymorphic types are available, is
to include generalized algebraic datatypes (GADTs), which can be
thought of as extending ordinary polymorphic datatypes with extra
equality constraints.~\cite{middelkoop2010lean} Such equality
constraints allow more invariants to be expressed within the type
language---for instance, it is possible to encode invariants about
various kinds of balanced-tree structures.
We anticipate that GADTs
will fit naturally in to our type- and example-directed synthesis
approach because they enjoy a tractable type inference
algorithm~\cite{schrijvers2009complete}. The additional equality
constraints imposed by GADTs should provide information that can help
prune the search space, though determining the most fruitful way to
take advantage of the extra equalities is one clear direction for
research.
\paragraph*{Refinement Types.}
In 1991, Freeman and Pfenning introduced a notion of \emph{refinement type}
to Standard ML~\cite{freeman:refinement-types}. Refinement types extend
the ML type system with the ability to specify specializations of data
types. For instance, the standard list type definition:
\begin{verbatim}
datatype 'a list = nil | cons of 'a * 'a list
\end{verbatim}
can be refined by a refinement specification:
\begin{verbatim}
reftype 'a single = cons ('a, nil)
\end{verbatim}
The refinement defines a new subtype of lists that is inhabited
by lists with exactly one element. In Freeman's work,
programmers can use such refinement
types in conjunction with intersection and union types to provide
much richer specifications of programs than are possible in
the Standard ML type system. Since, Freeman's initial work,
many others have experimented
with effective variations and extensions~\cite{pfenning:refine-logic-frameworks,davies:refinement-checker,davies:refine-checking,vazou:abstract-refinements}
We believe that refinements, together with intersection types,
may provide an effective framework for more deeply understanding
synthesis from examples and for generalizing all of our existing
algorithms. More specifically, we have made the observation
that one of our existing
example function specifications (shown on the left) can be
reinterpreted the refinement type (shown on the right)!
\noindent
{\small
\begin{lstlisting}
{ [] => [] [] -> [] /\
| [0] => [0;0] [0] -> [0;0] /\
| [1;0] => [1;1;0;0] } [1;0] -> [1;1;0;0]
\end{lstlisting}
}
\noindent
We believe this surprising and exciting observation will open the door
to reinterpreting program synthesis with types and examples as pure
type-theoretic synthesis procedure (without examples). One of the main
advantages of this new viewpoint is that we hope to be able to exploit
even more of the theorem-proving literature as the link between intersection
types and conjunction should be relatively
clear whereas the sets of examples that we had been using had no obvious
theorem-proving analogue.
In addition, this insight suggests new kinds
of ``examples'' that we hope to be able to incorporate in to our system.
For instance, the refinement of lists first suggested, \verb+cons ('a, nil)+,
may be interpreted as a \emph{symbolic example}, with some parts of the
example concrete and other parts abstract. It specifies that a list
must have one element, but allows that element to be anything at all.
A symbolic example of the form:
\noindent
{\small
\begin{lstlisting}
{ cons ('a, nil) => cons ('a, cons ('a, nil)) }
\end{lstlisting}
}
\noindent
provides more information than the analogous concrete specification:
\noindent
{\small
\begin{lstlisting}
{ cons (0, nil) => cons (0, cons (0, nil)) }
\end{lstlisting}
}
\noindent
In the first case, we are effectively specifying an infinite family of
examples (a function) all at once.
We are eager to experiment with program specifications involving such
symbolic examples and to implement synthesis procedures for them.
We believe that in many cases,
such specifications may provide programmers with
betters ways to express their intentions than with examples alone.
In general, symbolic examples, intersection types and refinement
types provide a much more general specification mechanism than
simple types and examples provide separately.
\paragraph*{Dependent types and integration with SAT and SMT.}
Freeman and Pfenning's refinement types make it possible to
specify more properties of a program in that program's type signature
than is possible in a simply-typed or polymorphically typed program.
However, they do not allow specifications to use the rich
theories, such as those for uninterpreted functions, arithmetic, sets
and arrays, available in modern SAT and SMT solvers such as Z3~\cite{Z3}.
We also plan to investigate extensions that include
dependent types $\Pi x{:}\tau_1.\tau_2$ and constraints $\{x{:}\tau \, | \, P(x)\}$.
Such types will provide mechanisms for more precise specifications
than would otherwise be possible with refinements alone. In order
to solve synthesis problems in the contexts of such constraints,
we anticipate having to combine our type-theoretic approaches with the
SAT- and SMT-based synthesis approaches used in other
work~\cite{alur-fmcad-2013,solar-lezama-thesis-2008,vu-pldi-2014}.
A particularly compelling use for such richer, potentially dependent
types, is to augment the language with primitives for strings and
regular-expression refinement types. Such types have found use in the
context of semi-structured (e.g. XML or HTML) data
transformation~\cite{hosoya2003xduce,benzaken2003cduce} and in the work on
bi-directional programs, or \textit{lenses}~\cite{foster2007combinators,bohannon2008boomerang},
that provide succinct ways of reconciling differing views with updates
of data. Relatively recent work~\cite{henglein2011regular} has shown how to give a computational interpretation to
regular expression types more generally. Here, the richer type
structure should suggest domain-specific synthesis strategies that we
intend to apply in the context of data transformation
applications---see Section~\ref{sec:appl} for a description of one use case for
configuration files.
% - used in xduce, cduce, lens languages
% - for working with strings and semi-structured data like XML
% - synthesize program transformations
% - Henglein
% - [forward reference to example applications]
\paragraph*{Effectful programs.}
The various ways of enriching type systems described above pertain
mainly to the synthesis of \textit{pure} programs. However, many
applications of program synthesis are best framed in languages that
support \textit{effects}, which allow for features like exceptions,
resource management, concurrency, or other kinds of I/O. We will
therefore investigate ways to extend our type-directed synthesis
techniques so that they apply in these settings as well.
The programming languages research community has identified several
type-theoretic approaches for expressing effectful computations, and
they should be conducive to our type-directed synthesis approach. One
simple example is a type system that tracks exceptions~\cite{leroy2000type}. In this case, the presence of an exception
in a function' signature constrains its implementation so that either
it relies another function that raises a (compatible) exception or it
raises the exception itself. Dually, the lack of an exception in the
function's signature indicates either the function has a local
exception handler or it does not rely on other exception-producing
functions. In any case, these types constrain the search space of
possible implementations. A related approach is to use
\textit{monads}~\cite{moggi1991notions,wadler1998marriage}, which are well known to be able
to describe effectful computations and also have a close correspondence
with the type-and-proof-theoretic search strategies that we employ.
In particular, the notion of $\beta$-normal
$\eta$-long programs can be naturally extended to include monadic
canonical terms that define where effects may occur~\cite{concurrent-logical-framework-propositional,watkins2003concurrent}. How best to utilize such insights in program synthesis is an important
question we intend to investigate.
Another promising avenue is to explore program synthesis in the
context of \textit{linear types}~\cite{Girard87}, and the related notion
of \textit{session types}~\cite{CairesP10concur,VasconcelosGR06multithreaded,wadler2014propositions}. These techniques
permit the program's type system to capture properties of resource
usage or communication patterns. For instance, recent work by
Pfenning and Wadler has shown how linear types give rise to session
types that can encode protocols. Here, the strong connection to logic
and proof theory suggest that our type-directed synthesis techniques
could be used to find implementations of protocols.
One challenge in the synthesis of effectful programs is how to extend
the ``input/output'' examples: I/O examples alone are not
sufficient in the presence of effects---we also need specifications of
the intended side effects. One possibility is to define classes of
behaviors, possibly by combining monadic or linear type systems with
richer SMT-based constraints.
\subsection{Theory}
Our research philosophy demands that we make progress on theoretical
analysis of our programming languages and tools at the same time as
we implement them: theory informs practice and practical experiments
suggest new theoretical avenues to pursue. We will adhere to this
philosophy as this project evolves.
\paragraph*{Semantics of Underdefined Programs.}
Our current prototype involves extending OCaml with ``holes'' that
can be filled with synthesized programs. The first natural question
that arises is how to give a semantics to such a language. Our
initial investigations in to this topic have lead us to the work of
Denney~\cite{denney:underdeterminism}, who developed a semantics for a simply-typed
lambda calculus with ``underdefined'' subprograms. Denney was interested
in studying the semantics of manual program refinement from specifications
as opposed to program synthesis. Nevertheless, his basic theory may be
applicable, though he does not consider polymorphism, recursion, sum
types or dependent types. We plan to explore extensions of this theory
to provide a semantics and equational theory for our language.
\paragraph*{Soundness and Completeness.}
Two key meta-theoretic properties of our synthesis systems are
\emph{soundness} and \emph{completeness}. In order for our system
to be \emph{sound}, any program we synthesize must be well-typed
and consistent with our examples. We have discovered that in the
presence of recursive functions, the soundness of our divide-and-conquer
algorithms is somewhat subtle. More specifically, when synthesizing
a sub-part of a recursive function $f$, it is tempting to use a recursive
call $f e$ in certain circumstances. However, we have discovered that in
the context of our current algorithms, it is
only sound to do so if the correctness of the call $f e$ can be validated
\emph{locally} by an example. However, this leads us to require more examples
than expected. We plan to investigate this issue more deeply, both in
theory and in practice --- it is closely connected to the
\emph{recursive back-patching} procedure (see Section~\ref{sec:core}),