U.S. patent application number 17/160098 was filed with the patent office on 2021-06-24 for bidirectional evaluation for general- purpose programming.
The applicant listed for this patent is THE UNIVERSITY OF CHICAGO. Invention is credited to Ravi Chugh, Mikael Mayer.
Application Number | 20210191717 17/160098 |
Document ID | / |
Family ID | 1000005461145 |
Filed Date | 2021-06-24 |
United States Patent
Application |
20210191717 |
Kind Code |
A1 |
Mayer; Mikael ; et
al. |
June 24, 2021 |
BIDIRECTIONAL EVALUATION FOR GENERAL- PURPOSE PROGRAMMING
Abstract
A method of facilitating bidirectional programming of a user may
include receiving an original program source code and evaluating
the original program source code in the forward direction to
generate a program output. The evaluation may occur in a
programming environment. The program output may be displayed, and
an indication of the user corresponding to modifying the program
output may be received. The modified program output may be
evaluated to generate an updated program source code, wherein the
updated program source code, when evaluated, may generate the
modified program output. The modified program output may be
displayed in a display device of the user. A computing system
including a bidirectional programming environment may also be
included.
Inventors: |
Mayer; Mikael; (Chicago,
IL) ; Chugh; Ravi; (Chicago, IL) |
|
Applicant: |
Name |
City |
State |
Country |
Type |
THE UNIVERSITY OF CHICAGO |
Chicago |
IL |
US |
|
|
Family ID: |
1000005461145 |
Appl. No.: |
17/160098 |
Filed: |
January 27, 2021 |
Related U.S. Patent Documents
|
|
|
|
|
|
Application
Number |
Filing Date |
Patent Number |
|
|
PCT/US19/43846 |
Jul 29, 2019 |
|
|
|
17160098 |
|
|
|
|
62711252 |
Jul 27, 2018 |
|
|
|
Current U.S.
Class: |
1/1 |
Current CPC
Class: |
G06F 8/71 20130101; G06F
8/34 20130101 |
International
Class: |
G06F 8/71 20060101
G06F008/71; G06F 8/34 20060101 G06F008/34 |
Goverment Interests
GOVERNMENT LICENSE RIGHTS
[0002] This invention was made with government support under grant
number 1651794 awarded by the National Science Foundation. The
government has certain rights in the invention.
Claims
1. A method of facilitating bidirectional programming of a user,
comprising: receiving, via a processor, an original program source
code, evaluating the original program source code to generate a
program output, displaying, in a first display device of the user,
one or both of (i) the original program source code, and (ii) the
program output, receiving an indication of the user corresponding
to modifying the program output; and evaluating the modified
program output to generate an updated program source code, wherein
the updated program source code, when evaluated, generates the
modified program output.
2. The method of claim 1, wherein the original program source code
includes one or more instructions encoded in a general-purpose
computer programming language.
3. The method of claim 1, wherein evaluating the original program
source code to generate a program output includes generating HTML
output.
4. The method of claim 1, wherein evaluating the modified program
output to generate an updated program source code includes a
tail-recursive optimization, a merging closure optimization, and/or
an edit difference optimization.
5. The method of claim 1, wherein evaluating the modified program
output to generate an updated program source code includes applying
a user-defined lens to the modified program output.
6. The method of claim 1, further comprising, displaying, in a
second display device of the user, one or both of (i) the updated
program source code, and (ii) the modified program output.
7. The method of claim 6, wherein the user interacts with the
second display device of the user to accept the modified program
output.
8. The method of claim 6, wherein the updated program source code
includes a plurality of ambiguous candidate source codes, each of
which, when evaluated, generate the modified program output.
9. The method of claim 8, wherein displaying the updated program
source code is based on applying a heuristic to automatically
select one of the plurality of ambiguous candidate source
codes.
10. A computing device configured for bidirectional programming of
textual data by a user via a graphical user interface, the
computing device comprising: a least one display device, at least
one processor, at least one memory, including computer-readable
instructions that, when executed by the at least one processor,
cause the computing device to: display, in the at least one display
device, an original program source code and a program output
corresponding to the evaluated original program source code,
receive, via the graphical user interface, an indication of the
user corresponding to modifying the program output; and evaluate
the modified program output to generate an updated program source
code.
11. The computing device of claim 10, wherein the original program
source code includes one or more instructions encoded
general-purpose computer programming language.
12. The computing device of claim 10, including further
instructions that, when executed cause the computing system to:
output HTML.
13. The computing device of claim 10, including further
instructions that, when executed, cause the computing system to:
generate an updated program source code using a tail-recursive
optimization, a merging closure optimization, and/or an edit
difference optimization.
14. The computing device of claim 10, including further
instructions that, when executed, cause the computing system to:
apply a user-defined lens to the modified program output.
15. The computing device of claim 10, wherein the updated program
source code includes a plurality of ambiguous candidate source
codes, each of which, when evaluated, generate the modified program
output.
16. The computing device of claim 15, including further
instructions that, when executed, cause the computing system to:
apply a heuristic to automatically select one of the plurality of
ambiguous candidate source codes.
17. The computing device of claim 10, including further
instructions that, when executed cause the computing system to:
display, in the at least one display device, one or both of (i) the
updated program source code, and (ii) the modified program
output.
18. The computing device of claim 17, including further
instructions that, when executed, cause the computing system to:
listen for a graphical user interface event corresponding to an
action of a user, wherein the action represents the user's
acceptance of the modified program output.
19. A computing device including a computer-readable medium storing
a programming environment application that, when activated, causes
the computing device to: evaluate, in a forward direction, an
original program source code to generate an output, receive, via an
input device, an indication of a user, the indication affecting the
state of the output, evaluate, in a reverse direction, the output,
to generate an updated program source code; and display, in a
display screen, the output and the updated program source code.
20. The computing device as recited in claim 19, wherein the
updated program source code includes a plurality of ambiguous
candidate source codes, and wherein the programming environment
application further causes the computing device to: display, in the
display screen, the plurality of ambiguous candidate source codes,
receive, via the input device, a selection of the user
corresponding to one of the plurality of ambiguous candidate source
codes; and in response to the selection of the user, display, in
the display screen, the one of the plurality of ambiguous candidate
source codes.
Description
CROSS-REFERENCE TO RELATED APPLICATION
[0001] This application is a continuation of International Patent
Application No. PCT/US/2019/043846, filed Jul. 29, 2019, which
itself claims the benefit of and priority to U.S. Provisional
Application 62/711,252, filed Jul. 27, 2018. The contents of the
PCT and provisional application are incorporated herein in their
respective entireties.
FIELD OF THE DISCLOSURE
[0003] The present disclosure generally relates to a system and
method for facilitating bidirectional program evaluation.
BACKGROUND
[0004] The background description provided herein is for the
purpose of generally presenting the context of the disclosure. Work
of the presently named inventors, to the extent it is described in
this background section, as well as aspects of the description that
may not otherwise qualify as prior art at the time of filing, are
neither expressly nor impliedly admitted as prior art against the
disclosure.
[0005] Direct manipulation user interfaces have been developed for
a wide variety of domains, such as word processing, diagrams,
spreadsheets, data visualizations, presentations, and web
applications. Such interfaces allow users to experiment with the
digital objects they are creating in rapid fashion, where small
edits and actions are immediately and interactively displayed.
Despite the benefits of a direct manipulation graphical user
interface (GUI), programmers often choose to write programs to
generate content, in order to harness abstraction capabilities that
are severely limited in typical direct manipulation systems. For
example, programmers may use languages and libraries such as p5.js,
Processing, JavaScript, Ruby, Elm, Microsoft PowerPoint,
L.sup.AT.sub.EX , Racket Slideshow, and D3. Users of such libraries
may write code that, when executed, causes output to be created,
including presentation data (e.g., slides, graphics, styled text,
data-driven documents, and/or visualizations). The output may be
encoded in an open file format such as Hypertext Markup Language
(HTML), a semi-open format such as Microsoft Word Document (.doc)
format, or a closed-source/proprietary format.
[0006] However, the power of programming creates non-negligible
complexity. Namely, to change the output of a program in a
traditional programming environment, the user/programmer must edit
the source code, run (e.g., compile, interpret, etc.) it again, and
view the new output, often repeating this loop for a long time
(e.g., months or even years) while developing a project. This cycle
is sometimes referred to as the "edit-run-view" or
"edit-compile-run" cycle, and it wastes users' time. The amount of
time and effort spent in this way is particularly wasteful when
successive edits to the program and the effects of those edits on
the resulting output are small and/or narrow in scope. The current
state of the art requires users to edit code of a computer program
even in cases where editing the output of the computer program
might seem the most logical and/or natural thing to do, from the
perspective of a user. This is true even when changes to the output
are small, relative to the amount of work that must be performed in
the code to produce those changes.
[0007] Historically, two primary approaches have been advanced with
the goal of allowing programs to run "in reverse." However, both
approaches suffer from serious drawbacks. A first approach,
developed in bidirectional programming languages, allows certain
computations to be specified as "lenses," wherein a "get" function
for forward-evaluation is paired with a "put" function for
backwards-evaluation, where the latter serves as a sensible
complement to the former. Although lenses are a powerful tool for a
variety of tasks--including transformations over relational,
semi-structured, and unstructured data--lenses are not a solution
for automatically reversing the computation of an arbitrary program
written in a general-purpose computer programming language.
[0008] Another challenge in the literature on lenses relates to
defining a reversible list map function. For example, some prior
art approaches define lists using records, and the mapping function
is parameterized by a lens. However, that approach cannot add or
remove elements, nor change the original lens. Some prior art
approaches include a set of well-typed lens combinators for
creating HTML forms that can write back the data, including
inserting and deleting elements. However, these approaches require
lenses at every step, and it is not possible to modify the style
from the output (e.g., by removing a <br> tag) without
changing it directly in the source code. At least one prior art
approach overcomes the problem of inserting and deleting elements
by duplicating elements from the output. Another approach
acknowledges that a modified function constant causes the update
procedure to fail.
[0009] A second approach aims to reverse arbitrary programs by an
interpreter first recording value traces to track the provenance of
how values are computed, and then, when a user makes small changes
to output, solving updated value-trace equations to synthesize
repairs to the program. This approach suffers from numerous
limitations, including that although tracing and updates for
numeric values are supported, the tracing of other types of simple
or more complex values is not supported. Also, this approach does
not allow advanced users to customize the behavior of the
algorithm, which represents a significant limitation in practice,
because no single update algorithm for arbitrary programs can work
well in all use cases. Furthermore, even assuming for the sake of
argument that the tracing approach could be extended to address the
aforementioned limitations, all computations would be required to
be traced even if many or most values were never updated by the
user. For programs where the subset of values that are directly
manipulated becomes a small fraction, the space overhead of this
approach could become a bottleneck, as is often the case for other
types of programs with heavy tracing requirements (e.g., omniscient
debuggers).
[0010] Prior work in automated program repair and synthesis,
bidirectional programming, and combining programming languages with
direct manipulation user interfaces has included attempts to
generate and manipulate Scalable Vector Graphic (SVG) documents,
and has proposed that GUI features should be co-designed with
program transformations that aim to make "large," structural, often
semantics-changing edits that codify the user actions. Prior work
has been proposed allowing "small" changes to output values to be
reconciled through local updates to the program. However, such
approaches record value traces for all numeric values, and when the
user updates a number, the corresponding value-trace equation is
immediately solved, applied to the program, and the new output is
rendered. The resulting workflow provides a continuous, "live"
interaction for equations that can be solved in almost real-time.
When multiple valid solutions are found, the prior approaches may
employs simple heuristics to automatically choose an output,
favoring continuous updates over user interaction to resolve
intent. In such systems, arbitrary types of values cannot be
changed, custom update behavior cannot be defined, and time
overhead (from re-evaluation) is traded to save space overhead
(from recording traces).
[0011] Evaluation update is similar to program repair, and tools to
repair HTML-producing programs do exist (e.g., for PHP Hypertext
Preprocessor (PHP)). Such tools fix string literals out of context,
or globally based on a set of corrected input/outputs, by creating
string equations and minimizing the number of string literals to
correct. Although these approaches may provide acceptable results
in some cases, such approaches are not able to correct strings that
were computed, stored in and/or retrieved from variables, which is
a very common practice if the HTML template comes from another
file. The prior approaches are unable to back-propagate
modifications either on constants or on variables, and cannot deal
with various string transformations.
[0012] In fact, the conventional approaches for writing inverse
evaluators, or "unevaluators" include serious shortcomings. First
example, the evaluator is separate from the unevaluator and
consequently, ensuring that the unevaluator is actually in sync
with the evaluator is error-prone, especially because of complex
pattern matching, partial closure evaluation, and so on. Second,
the evaluator is called from the unevaluator, and without caching
intermediate results, the update algorithm is much slower than the
evaluator because it has to repeatedly call the evaluator
itself.
[0013] In summary, although known bidirectional programming
languages can evaluate certain classes of functions in reverse,
current approaches do not enable evaluation of functions in reverse
for arbitrary programs written in general-purpose languages.
BRIEF SUMMARY
[0014] In one aspect a method of facilitating bidirectional
programming of a user includes receiving an original program source
code, evaluating the original program source code to generate a
program output, displaying one or both of (i) the original program
source code, and (ii) the program output in a first display device
of the user, receiving an indication of the user corresponding to
modifying the program output, and evaluating the modified program
output to generate an updated program source code, wherein the
updated program source code, when evaluated, generates the modified
program output.
[0015] In another aspect a computing device configured for
bidirectional programming of textual data by a user via a graphical
user interface includes a least one display device, at least one
processor, and at least one memory. The memory may include
computer-readable instructions that, when executed by the at least
one processor, cause the computing device to display, in the at
least one display device, an original program source code and a
program output corresponding to the evaluated original program
source code. The instructions may further cause the computing
device to receive an indication of the user corresponding to
modifying the program output, and to evaluate the modified program
output to generate an updated program source code.
BRIEF DESCRIPTION OF THE DRAWINGS
[0016] FIG. 1a depicts a syntax of a bidirectional programming
language supporting bidirectional evaluation for programs,
according to one embodiment,
[0017] FIG. 1b depicts a syntax of a bidirectional programming
language supporting bidirectional evaluation of dynamic code,
according to one embodiment,
[0018] FIG. 2a depicts evaluation semantics for bidirectional
programming for programs, according to one embodiment,
[0019] FIG. 2b depicts evaluation semantics for evaluation and
update for arithmetic operations in a bidirectional programming
language, according to one embodiment,
[0020] FIG. 2c depicts a rule set implementing evaluation and
update for lists in a bidirectional programming language, according
to one embodiment,
[0021] FIG. 2d depicts a rule set implementing evaluation and
update for user-defined lenses and primitive helper functions,
according to one embodiment,
[0022] FIG. 3a depicts a custom lens for lists with one or fewer
elements,
[0023] FIG. 3b depicts a custom lens for lists with an arbitrary
number of elements,
[0024] FIG. 4 depicts a table of benchmark data relating to
numerous example programs,
[0025] FIG. 5a depicts a programming environment including an
initial prototype of a computer program, according to an
embodiment,
[0026] FIG. 5b depicts the programming environment of FIG. 5a,
further including a pop-up window including a result of reconciling
a user's edits to the rendering of HTML output generated by the
computer program source code with the computer program source
code,
[0027] FIG. 5c depicts the computer programming environment of FIG.
5b, further including a popup window including a result of
reconciling a user's edits to the rendering of HTML output
generated by the computer program source code with the computer
program source code, wherein the edits resulted in ambiguity,
[0028] FIG. 5d depicts a computer programming environment for
allowing a user to modify the program output using built-in tools
of a web browser, according to an embodiment,
[0029] FIG. 5e depicts a computer programming environment wherein
the user directly edits the Document Object Model (DOM) of an HTML
document output in the programming environment,
[0030] FIG. 5f depicts the computer programming environment of FIG.
5e, wherein the user uses a styles editor of a web browser to add a
new attribute directly to DOM,
[0031] FIG. 5g depicts the computer programming environment of FIG.
5f, wherein updated output is displayed and the user is provided
with a graphical user interface element depicting the evaluated
changes and an option to revert the changes,
[0032] FIG. 5h depicts a computer program source code for
implementing certain aspect of the graphical interface facility of
FIG. 5i, according to an embodiment,
[0033] FIG. 5i depicts a computer programming environment wherein a
code library includes a graphical user interface element in the
output display which allows the user to add a structural element by
interacting with the graphical user interface element,
[0034] FIG. 5j depicts an HTML module providing helper functions
for creating HTML elements, according to an embodiment,
[0035] FIG. 6 depicts a system diagram for implementing the present
techniques, according to some embodiments and scenarios,
[0036] FIG. 7 depicts a flow diagram for performing bidirectional
programming, according to an embodiment,
[0037] FIG. 8 depicts an example back-propagation algorithm,
according to an embodiment,
[0038] FIG. 9 depicts call-by-value evaluation semantics, according
to an embodiment,
[0039] FIG. 10 depicts call-by-name evaluation semantics, according
to an embodiment; and
[0040] FIG. 11 depicts bidirectional Krivine evaluation semantics,
according to an embodiment.
DETAILED DESCRIPTION
[0041] Although the following text sets forth a detailed
description of numerous different embodiments, it should be
understood that the legal scope of the description is defined by
the words of the claims set forth at the end of this text. The
detailed description is to be construed as exemplary only and does
not describe every possible embodiment since describing every
possible embodiment would be impractical, if not impossible.
Numerous alternative embodiments could be implemented, using either
current technology or technology developed after the filing date of
this patent, which would still fall within the scope of the
claims.
[0042] It should also be understood that, unless a term is
expressly defined in this patent using the sentence "As used
herein, the term" "is hereby defined to mean . . . " or a similar
sentence, there is no intent to limit the meaning of that term,
either expressly or by implication, beyond its plain or ordinary
meaning, and such term should not be interpreted to be limited in
scope based on any statement made in any section of this patent
(other than the language of the claims). To the extent that any
term recited in the claims at the end of this patent is referred to
in this patent in a manner consistent with a single meaning, that
is done for sake of clarity only so as to not confuse the reader,
and it is not intended that such claim term be limited, by
implication or otherwise, to that single meaning. Finally, unless a
claim element is defined by reciting the word "means" and a
function without the recital of any structure, it is not intended
that the scope of any claim element be interpreted based on the
application of 35 U.S.C. .sctn. 112(f).
[0043] In contrast to prior approaches, the present application
discloses a method and system of bidirectional evaluation for
programs in a full-featured, general-purpose functional programming
language. The system and method of bidirectional evaluation with
direct manipulation described herein facilitates the ability of the
user and/or author of a computer program to directly manipulate the
output of the program, and the ability of the user/author to
evaluate the program "in reverse," using the manipulated output to
automatically compute necessary edits to the source code of the
program. In the bidirectional evaluation techniques described
herein, arbitrary programs in a general-purpose functional language
can be run in reverse in order to produce useful edits to the
program. The system and method provide a straightforward and
natural way for users to express changes to source code by directly
manipulating the output of programs, and to express changes to the
output of programs by directly manipulating the source code. The
method and system allow evaluation of the program "in reverse,"
using the new expected output as specified by the user to help
synthesize the necessary program repairs/edits.
[0044] The methods and systems may synthesize updates to the
program based on changes to the output of the program using an
evaluation update algorithm, or simply, update algorithm. The
update algorithm may include retracing the steps of the original
evaluation and rewriting the program as needed to reconcile
differences between the original source code and the output.
Compared to typical evaluation, the evaluation update algorithm
receives an expected output value as an argument to help synthesize
repairs to the expression such that it computes the expected value.
Further, programmers may define custom lenses to augment the update
algorithm with more advanced or domain-specific program updates.
Herein, the user of the methods and systems may alternately be
referred to as a "user," a "developer," a "programmer," etc. In
some cases, a first person may author code, and a second person may
manipulate the output of the authored code. In some cases, the
first person and/or the second person may be non-programmers or
non-technical users (e.g., a graphic designer). In an embodiment,
the manipulation of output of the authored code may be performed by
a computer software process, such as a set of computer-readable
instructions. Example custom update lenses for several common
functional programming patterns are described herein, as extensions
to the "built-in" evaluation update algorithm.
[0045] Sometimes differences may be propagated that prevent the
entire program from being unevaluated. The present techniques allow
the use of lenses to handle such differences, to handle only the
concerned portions. The update methods may also handle and produce
differences. In particular, in place of an ordinary function
application e.sub.get e, users of the present techniques can define
a lens application applyLens {apply=e.sub.get; update=e.sub.put} in
which case, the unevaluation algorithm uses the designated update
function e.sub.put to help compute a new expression e' to replace
the argument e.
[0046] In addition to an evaluation relation ev that evaluates
expression e to value v, an evaluation update (or simply, update)
relation e v'e' is described herein which, given an expected value
rewrites the original expression e to e'.
[0047] Evaluation update may proceed by comparing the original
output value v with the goal v', and synthesizing repairs to e such
that, ideally, the new program e' evaluates to v'. Evaluation
update may be defined for arbitrary expressions e producing
arbitrary types of values v. The approach described herein may
include uninstrumented evaluation such that expressions are
re-evaluated as needed during update.
[0048] The following discussion includes example embodiments of a
direct manipulation programming environment/system for
interactively editing documents (e.g., HTML documents), wherein a
user may author programs in a language to generate output, wherein
the user may directly manipulate the output using a GUI such as a
web browser, and wherein the output is evaluated "in reverse" to
generate an updated program source code. In one embodiment, a
built-in facility of a web browser may be used to manipulate the
HTML output, such as a DOM inspector. In another embodiment, the
program itself may include instructions which provide the ability
to add, modify, and/or delete structural elements from the output.
In some embodiments, when the user directly manipulates the output
using the GUI, the update algorithm may reconcile the changes in
the output with the source code of the program.
Example Language Syntax
[0049] The following includes a description of an example
embodiment in which the concepts of bidirectional evaluation for
programs have been implemented in a full-featured, general-purpose
functional programming language. Several optional performance-based
optimizations to the evaluation update algorithm are also
described. However, in some embodiments, the concepts described
herein may include a programming language or paradigm wherein fewer
or more constructs are included. In some embodiments, a procedural,
imperative, and/or object-oriented language may implement the
bidirectional evaluation concepts. In some embodiments, the
full-featured, general-purpose functional programming language
embodiment described herein provide unique benefits for the
integration of programmatic and direct manipulation.
Fundamental Syntax
[0050] FIG. 1a depicts a fundamental syntax for a lambda-calculus
that models the language supported by the present techniques is
presented. FIG. 1a includes definitions for expressions e, spread
across three lines and corresponding respectively to: [0051]
constants c, variables x, function application e.sub.1 e.sub.2,
list construction e.sub.1::e.sub.2, record extension {e|f=e.sub.f},
record field projection e.f, [0052] (simple and recursive)
let-bindings let x e.sub.1 e.sub.2, conditionals if e.sub.1 e.sub.2
e.sub.3, and case expressions case e (p.sub.1 e.sub.1) . . . ; and
[0053] evaluation update.
[0054] The fundamental syntax of FIG. 1a includes constants c
including numbers n, booleans b, strings s, the empty list [ ], the
empty record { }, and built-in primitive operators, including
operators for arithmetic, logic, and custom lenses. In particular,
the primitive operators updateApp, diff, and merge facilitate the
definition of custom lenses, which are discussed below. The values
v include constants, closures (E, .lamda.pe) where the environment
E binds free variables in the body of the function .lamda.pe, and
lists and records with zero or more components.
Extended Syntax
[0055] The fundamental syntax of FIG. 1a may be extended with
additional programming conveniences to support programming
practical applications, optimizations and other enhancements to
turn the evaluation update relation into an algorithm suitable in a
practical setting, and user interfaces for manipulating HTML output
values and choosing program updates. For example, in addition to
the constants, lists, and records presented in the fundamental
syntax, the enhanced syntax may support tuples and user-defined
data types, which are transformed (i.e., de-sugared) internally to
records. The extended syntax may also support value-indexed
dictionaries with an arbitrary number of bindings. The syntax
compatible with an ML-style type system, and some embodiments may
include type checking. In still further embodiments, variable
definition strings, string interpolation, and dynamic code
evaluation, and other useful constructs may be included, according
to some scenarios.
[0056] Programs that generate HTML and parse HTML typically perform
a large amount of string processing and JavaScript code generation,
and utilize common data structures. Language extensions
facilitating such tasks are described in the following
sections.
Regular Expressions
[0057] The extended implementation includes two common regular
expression operators. The first operation, extract re s, takes a
regular expression re (as a string) and a string s to transform,
and optionally returns a list of all the groups of the first match
of re to s. The update semantics include taking a set of
non-overlapping modified groups--taken greedily from the right--and
pushing them back to their original place in the original string.
For example, extract "b(.)" "bab" produces Just ["a"]. If the
result is updated to Just ["x"], the string s is updated to
"bxb".
[0058] The second operation, replace re f s, takes a regular
expression re, a function f, and a string s to transform. The
function argument provides access to the match information,
including the index into the string, the subgroups and their
positions, the global match, and the replacement number. The
function uses this information to produce a string. Interestingly,
the final string after replacement is an interleaved concatenation
of strings that did not change and applications of the lambda to
the record associated to each match. For example, in the string
"arrow", if the expression "(rr|w)" is replaced with the function
f=.lamda.mif mmatch=="w" then "r" else "rm", then an expression is
created that looks like
"a"+f{match="rr"}+"o"f{match="w"}.
This expression may be used both for evaluation and update. For
update, the update procedure may first be run on this expression.
Then, in the environment, an updated function f' may be recovered.
To update the original string s, the information about the matches
that changed (including the subgroups) is gathered and applied to
s.
[0059] Using the reversible extract operation, a String library may
be constructed which includes reversible variants of several common
string-processing operations: take, drop, match, find, tolnt, trim,
uncons, and sprintf.
Long String Literals
[0060] Many languages allow string literals to refer to variables
or expressions, which are then expanded (i.e., interpolated). The
present techniques provide long string literals--distinguished by
triple double quotes and which may span multiple lines--that
support string interpolation of expressions (written """(e)"""). To
further facilitate string processing tasks, the present techniques
also allow variables to be defined within long string literals
(written """let x e; s""").
Dynamic Code Evaluation
[0061] The present techniques allow for other common web
programming patterns to be achieved. For example, the present
techniques allow the dynamic computation of strings that are meant
to parse and evaluate as expressions. The present techniques
include a dynamic code evaluation primitive, eval e, for this
purpose. The evaluation and update rules follow.
[0062] FIG. 1b depicts an evaluation and update rule for dynamic
code, according to an embodiment. The evaluation rule E-Eval parses
the evaluated string s in the empty environment. This is distinct
from JavaScript, for example, where the generated code is evaluated
in the environment in which it was generated. If the programmer
would like for the generated code to have access to the
environment, the toString primitive--which converts the
environments of closures into nested let-expressions--can be used.
For example, toString (x2, .lamda.yx+y) evaluates to
"let x=2 in \y->x+y".
The update rule U-Eval uses the unparser to push the updated code
string s' back to the expression e that generated it. In some
embodiments, eval may be associated with an environment including a
string/value pair. The current environment may be captured using a
construct (e.g., CurrentEnv), and eval and update may be performed
in the current environment, a custom environment reflecting only
some functions, and/or a sandbox mode with no environment
Whitespace and Formatting
[0063] So that updated programs remain readable and conducive to
subsequent programmatic edits, the present techniques take care to
insert and remove whitespace in a way that respects the whitespace
conventions of surrounding expressions. To achieve this improvement
in usability, whitespace in between expressions and concrete syntax
tokens is recorded in an abstract syntax tree, and these are used
to determine how much whitespace to insert before, between, and/or
after newly created expressions. Bidirectional evaluation semantics
are introduced in the next section.
Example Bidirectional Update Semantics
[0064] FIG. 2a depicts the bidirectional evaluation semantics for a
subset of possible expression forms, including big-step evaluation
rules listed in the left column, and evaluation update (or simply,
update) rules listed in the right column. By analogy to
bidirectional type checking, evaluation may be thought of as "value
synthesis" and evaluation update as "value checking." An
environment-expression pair E e may be referred to as a program.
The evaluation update judgment E e v'E' e' states that "when
updating its output value to v', the program E e updates to E'
e'H." An outcome wherein only the expression (resp. environment)
changes may be conventionally referred to by stating that, "the
expression (resp. environment) updates to a new expression (resp.
environment)." Similarly, an evaluation update may be
conventionally referred to by stating, "push v' (or changes to v)
back to e." The evaluation update judgment does not refer to the
original value v produced by the program; if the original value is
needed by a premise of an update rule, it must be re-computed.
Update Rules
Simple Rules
[0065] Update rules may not recursively refer to the update
judgment. For example, the axiom U-Const states that, when updating
the output value of an expression to c', the expression c updates
to c'; the environment E remains unchanged. The rule U-Var states
that, when updating the output value of an environment to v', the
environment E updates to E'. This updated environment is like the
original, except that x is bound to the new value v'; the
expression x remains unchanged. The rule U-Fun states that, when
updating the output value of a program to the closure
(E',.lamda.pe'), the program E.perp..pi.pe updates to
E'.perp..lamda.pe'. Although updating closures in the output of a
program may be less common than other types of values, the U-Fun
rule is nevertheless crucial for the following derived rules.
[0066] FIG. 2b depicts evaluation and update rules for addition.
There are two update rules, U-Plus-1 and U-Plus-2, which,
respectively, re-evaluate the left or right operand (e.sub.1 or
e.sub.2) to a number (n.sub.1 or n.sub.2) and then push back the
updated difference (n'-n.sub.1 or n'-n.sub.2) entirely to that
operand. Because there are two update rules, there are two valid
program updates for addition expressions. Additional numeric
primitive operations (not shown in FIG. 2b) are handled in similar
fashion. The update rules are applied "automatically" to all
relevant (sub)expressions when trying to reconcile the program with
a new output value.
[0067] In some embodiments, arithmetic rules may produce unexpected
results. For example, pushing 4 to let x=1 in x+x may result in let
x=3 in x+x, which evaluates to 6. In practice this pattern may be
useful because it is non-blocking. Alternatively, it may be
possible to add rules to push back symbolic expressions such as w
to the x on the left side of the expression, and 4-w to the x on
the right side of the expression, such that after unification a
solver outputs the expected let x=2 in x+x.
[0068] The freeze e expression is semantically a no-op (E-Freeze in
FIG. 2a). However, this provides the programmer one simple way to
control the update algorithm, by requiring that the expression e
and values v it computes remain unaltered (U-Freeze in FIG.
2a).
Function Application
[0069] The treatment of function application is at the heart of the
evaluation update relation. FIG. 2a depicts a rule, E-App, for
evaluating function calls; to simplify the presentation, that rule
assumes that the function argument is a variable x rather than an
arbitrary pattern, as in our implementation.
[0070] The corresponding update rule is U-App. The first two
premises re-evaluate the function e.sub.1 to a closure (E.sub.f,
.lamda.xe.sub.f) and the argument e.sub.2 to a value v.sub.2. The
third premise pushes the updated value v' back through the function
call, specifically, through the function body e.sub.f, where the
closure environment is extended with the binding xv.sub.2 (as
during evaluation).
[0071] This produces a (potentially updated) function body e'.sub.f
and (potentially updated) environment E'.sub.f, xH v.sub.2' that is
structurally equivalent to the original (their domains are equal).
The value bound to x in the new environment is the (potentially
updated) value v'.sub.2.
[0072] At this point, the function body and its environment have
been updated. Next, the fourth premise pushes this program, in the
form of the closure (E'.sub.f, .lamda.xe'.sub.f), back to the
original function expression e.sub.1; the result is a new program
E.sub.1.perp.e'.sub.1. Then, the fifth premise pushes the new
argument v'.sub.2 back to the original argument expression e.sub.2;
the result is a new program E.sub.2.perp.e'.sub.2. Thus, the
updated function application expression is e'.sub.1 e'.sub.2.
[0073] What remains is to reconcile E.sub.1 and E.sub.2 with the
original E. The rules ensure that E.sub.1 and E.sub.2 are both
structurally equivalent to E, but each may have induced updates to
one or more bindings in E. As demonstrated with a subsequent
example, updated bindings may conflict--there may be variables y
such that E(y), E.sub.1(y) and E.sub.2(y) are all different. Next,
an approach to combining these environments is described.
Environment, Value, and Expression Merge
[0074] Several rules must consider multiple candidate environments
E.sub.1 and E.sub.2 when deciding how to update an original
environment E. For this purpose, a three-way environment merge
operation is defined: E.sub.1.sym..sub.EE.sub.2
(E.sub.1,xv.sub.1)(E.sub.2,xv.sub.2)=E',x(v.sub.1.sym..sub.vv.sub.2)
where E'=E.sub.1.sym..sub.EE.sub.2.
[0075] The three-way environment merge traverses the three
structurally equivalent environments, performing a three-way value
merge on each value binding. The value merge operation
v.sub.1.sym..sub.y v.sub.2 (not depicted) recursively traverses the
subvalues of three structurally equivalent values, until the rule
for base cases--for merging constants--chooses v.sub.2 if it
differs from v (even if v.sub.2 and v.sub.1 conflict) and v.sub.1
otherwise. It should be appreciated that other merge algorithms may
be employed, in some embodiments. For example, updates from the
left may be preferred in the merge algorithm, or all combinations
of choices may be propagated. One of the important benefits of the
present techniques is that the methods and systems for customizing
evaluation update disclosed herein enables users to readily define
such alternatives.
[0076] Closure values include expressions, so a three-way
expression merge operation e.sub.1 .sym..sub.e e.sub.2 is also
implemented (not shown) in similar fashion for closures.
List Construction
[0077] FIG. 2c depicts an evaluation rule (E-Cons) for list
construction, and a corresponding update rule (U-Cons) that
propagates changes to the head (resp. tail) value back to the head
(resp. tail) expression. The list construction and update rules
preserve the structure of existing cons expressions. In this
embodiment, structure-changing rules that add and/or remove cons
expressions are not included, due to the potential for introducing
ambiguity.
List Literals: Pretty Local Updates
[0078] The evaluation update rules discussed above may produce
updated (environments and) expressions that are structurally
equivalent to the original ones. Such structure-preserving changes
are referred to herein as local updates. Restricting changes to
local updates ensures a predictable class of "small" changes, but
is so restrictive that even seemingly benign changes are not
possible--e.g. updating the empty list expression [ ] with new
value [1].
[0079] Such updates may need to be allowed for practical purposes.
Therefore, some embodiments may include the rule U-List (FIG. 2c)
to allow insertion and deletion inside list literals that appear in
the program. This form of structural change as pretty local to
emphasize its limited effect on the program structure. The
statement [e.sub.1, . . . , e.sub.n] may be expressed, as syntactic
sugar for the nested list construction expression e.sub.1:: . . .
:: e.sub.n:: [ ], terminating with the empty list.
[0080] The helper procedure Diff(v, v') takes the original and
updated list values and computes a value difference .DELTA. (a
"delta"), in this case, a sequence of list difference
operations--Keep, Delete, Insert(v'), or Update(v'). In an
embodiment, the implementation of Diff uses a dynamic programming
approach which attempts to preserve as many contiguous sequences
from the original list as possible. The syntax of the evaluation
update judgment is reused for one that pushes back value
differences (rather than just values), with the subscript Diff to
help distinguishing the two syntaxes. The expression E [e.sub.1, .
. . , e.sub.n] .sub.Diff.DELTA.E' e' computes the list literal e'
that results from traversing the original list literal and the
difference operations; keeping, inserting, deleting, or updating
expressions as dictated by the difference. It should be appreciated
that some embodiments may include differences for insertion,
deletion, update, cloning, swapping, wrapping, unwrapping, and/or
any other suitable operations.
String, Records, and Dictionaries
[0081] Evaluation rules (not shown) for string concatenation
e.sub.1+e.sub.2, record literals {f.sub.1=e.sub.1; . . . }, and
record extension {e|f=e.sub.f} may also be included, in some
embodiments.
[0082] Dictionary values may be constructed using primitive
operators empty, get, insert, remove, and fromList. Update rules
for dictionaries may be implemented in much the same way as those
for lists. For example, the update rules may be based on dictionary
difference operations, analogous to the list difference operations
discussed above. Update rules for records and record extension may
also be implemented using similar principles as those discussed
with respect to lists above, except that those update rules may not
include insertions and/or deletions. Update rules for concatenating
strings and appending lists require a more nuanced approach, as
explained in the next section.
Customizing Evaluation Update
[0083] Because of the inherent expressiveness of the language,
evaluation update may not provide all possible intended behaviors
that users may desire. For example, the common evaluation and
update pattern below may not be handled by the update algorithm as
discussed thus far. In this example, metavariables f and x.sub.i
may refer to expressions and y.sub.i to refer to values.
E map f [ x 1 , x 3 , x 4 ] [ y 1 , y 3 , y 4 ] ##EQU00001## E map
f [ x 1 , x 3 , x 4 ] [ y 1 ' , y 2 , y 3 , y 4 ' , y 5 ] E ' map f
' [ x 1 ' , x 2 , x 3 , x 4 ' , x 5 ] Desired , but unavailable ,
program repair ##EQU00001.2##
[0084] The Diff operation computes the following alignment between
the original and updated values: that y.sub.1 and y.sub.4 have been
updated to y.sub.1' and y.sub.4', and new values y.sub.2 and
y.sub.5 have been inserted after (the updated versions of) y.sub.1
and y.sub.4. A user may desire an updated program of the form
indicated above, where f', x'.sub.1, and x'.sub.4 are updated
because of the two updated function calls fx.sub.i and fx.sub.4,
and where the synthesized values x.sub.2 are x.sub.5 are passed to
the function f', ideally producing the inserted values y.sub.2 and
y.sub.5. However, the evaluation update approach described so far
cannot synthesize repairs of the desired form above. Given the
definition
letrec map f list=case list of [ ]->[ ]; x::xs->f x:: map f
xs
the original list value [y.sub.1, y.sub.3, y.sub.4] is constructed
completely within the body of map: non-empty (cons) nodes are
created in the list=x::xs branch and the empty node is created in
the list=[ ] branch. To reconcile the updated list, y.sub.5 would
have to be inserted into the empty list [ ] in map, and element
y.sub.2 would have to be inserted into the cons-node. Besides the
fact that the present techniques strive to disallow structural
updates anywhere but E-List (cf. the "List Literals: Pretty Local
Updates" discussion, infra), such changes are not desirable because
the new cons-node would not be the result of applying f to
anything. Rather, the new cons-node would insert the same element
in between all elements in the output. Furthermore, map is a
library, the definition of which is, ideally, frozen.
[0085] Therefore, the evaluation update is unable to provide
simultaneous reasoning about structural changes to list values and
computations they pass through.
User-Defined Lenses
[0086] Rather than attempting to provide built-in support for map
and other common building blocks, the present techniques choose to
expose an API for users (or libraries) to customize the evaluation
update. Specifically, in place of any "bare" function f, the user
may additionally provide a second update function in the program
source code that specifies how to push values back to calls to
f.
[0087] A pair comprising bare and update functions forms a lens,
which is implemented using the syntax described above as a record
with the following type:
type alias Lens a b=
[0088] {apply: a->b, update: {input: a, outputNew:
b}->{values: List a} }
[0089] The above lens definition is typed, and the expression
applyLens e.sub.1 e.sub.2 syntactically marks the function
application as a lens application in lieu of a particular type.
Either a typed record or untyped record may be used, according to
some embodiments.
[0090] FIG. 2d includes an E-Lens rule, which projects the apply
field of the lens argument e.sub.1 and then applies it to the
argument e.sub.2. To push a new value v' back to the lens
application applyLens e.sub.1 e.sub.2, the U-Lens rule may use the
update function of the lens. The function argument is then
re-evaluated to v.sub.2 and, together with the new output v', is
passed to the e.sub.1.update function. Each value v'.sub.2 in the
values list of results is pushed back to the expression argument
e.sub.2 and then used as the argument of the updated function call
expression.
[0091] Because the lens mechanism in the FIG. 1a is intended to
provide a way to customize the built-in update algorithm, several
internal operators are exposed (updateApp, diff, and merge) which
custom update functions can refer to. FIG. 2d also describes the
semantics of these operations as they arise in the discussion
below. Because these operations are intended for use only in update
functions, evaluation rules are defined for these operators, but
update rules are not. However, in an embodiment, both update and/or
evaluation rules may be defined.
Optimizations
[0092] The present techniques include several additional
optimizations for the evaluation update relation, to form the basis
for a practical algorithm.
Optimization 1: Tail-Recursive Update
[0093] A direct implementation of the program update algorithm may
result in a call stack that increases with each recursive call to
update. Because the stack space in some interpretation environments
(e.g., in web browsers) is relatively limited, this recursive
approach may lead to exceptions for computational-intensive
benchmarks, even relatively small ones. Because the heap space is
usually less limited than stack space, a rewriting of the update
procedure to continuation-passing style makes the update procedure
tail-recursive and, thus, compiles to an optimized form in some
embodiments (e.g. in JavaScript the procedure compiles to a
while-loop). This transformation is compatible with a lazy list of
all solutions computed by the algorithm. In some embodiments, the
tail-recursive transformation can be used to repeatedly pause the
computation, for purposes of creating a non-blocking implementation
(e.g., in a single-threaded interpretation environment such as in
JavaScript).
Optimization 2: Merging Closures
[0094] Merging environments naively--following the definition of
E.sub.1 .sym..sub.E E.sub.2--may require exponential time, in some
embodiments. Each closure in the environment refers to the prefix
of the environment, which may have been modified. Hence, to compare
closures, their environments must be compared, and so on. In some
embodiments, merging bindings for only those variables which appear
free in the associated function bodies may be a critical
optimization step.
Optimization 3: Propagating and Merging Edit Differences
[0095] In some embodiments, an evaluation update judgment which
propagates expected values v', even though large portions of v' may
be identical to the original values v, is another potential
scalability issue. To address this, some embodiments may compute an
edit difference between v and v' which, together with those values,
serves as a compact but complete characterization of the changes.
For example, for numbers and booleans, the edit difference can be
represented as a Boolean flag indicating whether the value has
changed (i.e., whether U-Const needs to process this value).
[0096] For lists, the edit difference may be represented as a list
of index ranges associated with a number of insertions, a number of
removals, or an update based on a value difference. Edit
differences for other types of values, for expressions, and for
environments may also be analyzed, in some embodiments. These edit
differences may be propagated through the evaluation update
algorithm.
[0097] Further, edit differences may be exposed to user-defined
lenses, so that they can benefit from this optimized
representation. First, compared to the presentation of U-Lens in
FIG. 2d, the field outputOld may be included in the record argument
v.sub.3 to update: its value v is the original result of the
function call e.sub.1.apply e.sub.2. The update function can choose
to take outputOld into account when returning its list of new
argument values. Furthermore, to take advantage of the optimized
representation, the record argument may also contain a diffs field
that describes the edit differences that turn outputOld into
outputNew. In an embodiment, the update function may return a diffs
field (in addition to values). Then, the evaluation update
algorithm can continue to propagate changes using the optimized
representation. Reasoning with values and diffs can be thought of
as "states" and "operations", respectively, in the terminology of
synchronization. A foldDiff helper function may also be defined,
and used to define edit difference-based versions of the reversible
map and append lenses described above.
Correctness
[0098] In an embodiment, the ideal connection between evaluation
and update would be the following proposition:
Proposition 1 (Total Correctness of Update). If E.perp.ev (i.e. the
program E.perp.e evaluates to v) and E.perp.e v'E'.perp.e' (i.e.
when updating its output value to v', the program updates to
E'.perp.e''), then E'.perp.e'v' (i.e. the updated program will
evaluate to the updated value).
[0099] However, Proposition 1 is false, for two primary reasons.
The first reason is because conditional expressions, in particular
the U-If-True rule depicted in FIG. 2a, pushes the updated value
back to the true-branch, which was taken during the original
evaluation, optimistically assuming that the same branch will be
taken by the new program. The U-If-False rule (not shown) makes an
analogous assumption about the false-branch. In general, however,
these assumptions may be violated. For example, the expression
(.lamda.xif x==1 then x else 3) 1 evaluates to 1. If the user
updates the value to 2, the change will be pushed back to the
then-branch (and then back through the variable use to the function
argument), resulting in the updated expression (.lamda.xif x==1
then x else 3) 2. When evaluated, this expression takes the
false-branch and produces 3. If the false-branch happened to return
2, the updated program would "accidentally" produce the correct
updated value.
[0100] The second obstacle is that multiple updates may induce
conflicting program updates. For example, the expression
(.lamda.x[x, x]) 1 evaluates to [1, 1]. If updated to [0, 2], the
U-App, U-Cons, and U-Var rules, together with right-biased
environment merge, combine to update the program to (.lamda.x[x,
x])2, which, when re-evaluated, produces [2, 2].
[0101] To address the former problem, control-flow-alternating
updates could be disallowed. To address the latter problem,
environment merge could fail to produce an output when there are
conflicts, and the algorithm could require that all uses of a
variable in the output be updated in a consistent manner. However,
such variations may not be pursued in some embodiments, due to the
insight that total correctness is not of paramount importance for
the practicality of evaluation update in practice. For example,
several of the example use cases for direct manipulation
interaction depicted herein purposely alter control-flow (e.g.,
because of a change to a Boolean flag). Therefore, instead of
pursuing a strong correctness property, users are enabled to
consider the effects of various program updates using a programming
environment.
[0102] However, if all uses of a variable in the output are updated
consistently, then a roundtrip guarantee exists that the value
produced by the final updated program will be the same value that
was being pushed back. This idea is formalized in Proposition
2:
Proposition 2 (Weaker correctness property). Given an update tree,
if at any level where E.perp.e v'E'.perp.e' and
E'=E.sub.1.sym..sub.E E.sub.2 and each E.sub.i is the environment
over a sub-expression e'.sub.i, and furthermore for every variable
x updated in E' and every E.sub.i, either x was updated with the
same value in E.sub.i or it did not appear as a free variable in
e.sub.i, then E'.perp.e'v'.sub.1. Proof. Given an evaluation update
tree and the premises of Proposition 2, by structural induction on
the tree, if E.perp.e v'E'.perp.e', then E'.perp.e'v'. [0103] For
U-Const, given E.perp.c c'E'.perp.c', it is true that E.perp.c'c'
[0104] For U-Var, E'.perp.e' is equivalent to (E.sub.1, xv',
E.sub.2).perp.x where x does not appear in E.sub.2 so this
expression evaluates to v' because of E-Var. [0105] For U-Fun,
E.perp..lamda.xe (E', .lamda.xe')E'.perp..lamda.xe' so therefore E'
H)xe' (E', .lamda.xe') without further discussion. [0106] For
U-App, assuming E.perp.e.sub.1 e.sub.2 v'E'.perp.e'.sub.1e'.sub.2,
it is required that E.perp.e.sub.1(E'',.lamda.xe), E.perp.e.sub.2w,
E'', x.fwdarw.w.perp.e v'E''', x.perp.w'.perp.e', E.perp.e.sub.1
(E''',.lamda.xe')E.sub.1.perp.e'.sub.1, E.perp.e.sub.2
w'E.sub.2.perp.e.sub.2 and E'=E.sub.1.sym..sub.E E.sub.2. [0107] To
evaluate E'.perp.e'.sub.1e'.sub.2, evaluate E'.perp.e'.sub.1 and
E'.perp.e'.sub.2. First, by induction on the last three update
rules, one obtains that: E.sub.1.perp.e'.sub.1(E''',.lamda.xe'),
E.sub.2.perp.e.sub.2w' and E''', x.fwdarw.w'.perp.e'v'. [0108]
Second, by proving that: E'.perp.e'.sub.1 evaluates to the same
value as E.sub.1.perp.e'.sub.1, and E'.perp.e'.sub.2 evaluates to
the same value as E.sub.2.perp.e'.sub.2, by applying the evaluation
rules, E'.perp.e'.sub.1e'.sub.2v'. [0109] To prove two previous
points, it is sufficient to show that all free variables of
e'.sub.1 have the same value in E' and E.sub.1. If this was not the
case, it would mean that, either [0110] A free variable in e'.sub.1
was not updated in E.sub.1 but was updated in E.sub.2 with a new
value. According to Proposition 2's premises, this is not possible:
Because the variable appears in e'.sub.1, it should have been
updated in E.sub.1 with the same value [0111] A free variable in
e'.sub.1 was updated in E.sub.1 but the same variable was updated
in E.sub.2 with a different value, and the conflict resolution
chose E.sub.2's value. This is trivially not possible because the
premises of Proposition 2 states that there were no conflicts.
[0112] Similar reasoning proves the second point. Corollary 0.0.1
(Updated variables used once). Given an update tree, at any level
where E.perp.e v'E'.perp.e', if every variable x that was updated
in E' appear only once in e', then E'.perp.e'v'.sub.1. Proof. If
every updated variable x in E' appears at most (thus exactly) once
in e', then given that E'=E.sub.1.sym..sub.EE.sub.2 and that the
sub-expressions e.sub.1 and e.sub.2 are disjoint, the updated value
of x in E' comes from exactly one E.sub.1 (or resp. E.sub.2) and x
did not appear as a free variable in the other e.sub.2 (resp.
e.sub.1), Proposition 2 applies.
Example Lenses
Lens: Maybe Map
[0113] The following describes a simple example of mapping a
"MaybeOne" value, encoded as a list with either zero or one
elements, using the principles discussed above. FIG. 3a depicts a
definition of maybeMapSimple, which is frozen to prevent changes to
what is, effectively, a "library" function in some embodiments.
When reversing calls to maybeMapSimple, the built-in update
algorithm may be unable to deal with adding or removing elements
from the argument list (as with list map, discussed above).
[0114] Therefore, FIG. 3a defines a custom lens called
maybeMapLens. To deal with the case when the updated value includes
an element when there was none before, this lens is parameterized
by a default element. The lens functions apply and update take
arguments f and mx as a pair. The maybeMap definition on the last
line of FIG. 3a is defined as the application of this lens (wrapped
in applyLens) to its arguments packaged up in a pair. In the
forward direction, the apply function of maybeMapLens simply
invokes maybeMapSimple. In the reverse direction, the update
function uses a record pattern to project the input and outputNew
fields and handles two cases. If the new output my is [ ], the
updated MaybeOne value should be [ ], and the function f is left
unchanged--these are paired and returned as a singleton list of
result values. If the new output my is [y], the goal is to push y
back through a call of f. If the original input maybe value mx is
[x], then the function call f z=f x needs to be updated. If the
original input maybe value is [ ], however, there was no original
input; so, f z=f default needs to be updated.
[0115] To achieve this in FIG. 3a, the primitive updateApp operator
is used to push y back through f z using the built-in algorithm
(starting with rule U-App). The semantics of this operation, which
may correspond to E-Update-App of FIG. 2d, computes all possible
updated values v'.sub.2 and puts them in a In this way, updateApp
may expose the U-App rule to custom update functions.
[0116] Each value that comes out in results.values includes a pair
of a possibly-updated function newF and possibly-updated argument
newX. To finish, the second is wrapped in list and this pair forms
a solution. This function "bootstraps" from the primitive U-App
rule, lifting its behavior to the MaybeOne type. For example,
consider the function [0117] display [a, b, c]=[a, c+","+b] and two
calls to maybeMap defaultState display, where the definition [0118]
defaultState=["?", "?", "?"] serves as placeholder state data:
[0119] maybeRow1=maybeMap defaultState display [["New Jersey",
"NJ", "Edison"]]
[0120] maybeRow2=maybeMap defaultState display [ ]
[0121] As a preview, a specific example of this type of lens is
depicted in FIG. 5a), at line 14: updating the result of maybeRow1
to [ ] leads to updating the argument to [ ]. Updating the result
of maybeRow2 to [["New Jersey", "Edison, N.J."]] leads to updating
the argument to [["New Jersey", "NJ", "Edison"]]. Furthermore,
updating the result of maybeRow2 to [["New Jersey", "Edison N.J."]]
simultaneously inserts the appropriate three-element list and
changes the separator "," to " ". None of these three interactions
would be possible if instead calling maybeMapSimple display, which
is updated by the built-in algorithm alone.
Additional Lenses
[0122] The maybeMapLens definition demonstrates an approach for
dealing with updated transformed values--pushing them back through
function application, as usual--and for dealing with newly inserted
values--pushing them back through function application with a
default element. This approach may be extended, in some
embodiments, to a listMapLens definition that operates on lists
with arbitrary numbers of elements rather than just zero or one,
using a recursive traversal as follows: [0123] 1. the use of
primitive operator diff (for which E-Diff in FIG. 2d exposes the
Diff operation used by E-List) to align the original and updated
output lists, [0124] 2. the use of primitive operator merge (for
which E-Merge exposes the three-way value merge operation) to
combine multiple updates to the input function; and [0125] 3. when
inserting a new element into the output list, choosing to use an
adjacent element from the original list (rather than a
caller-specified default) to push back through a function call.
[0126] FIG. 3b defines a listMapLens for operating on lists with an
arbitrary number of elements. The high-level structure of update
uses a library function, Update.listDiff, defined in terms of a
more general primitive diff operator. Update.listDiff produces a
list of difference operations--KeepValue, DeleteValue,
InsertValue(v), and UpdateValue(v)--which are Leo encodings of
those returned by Diff in E-List. The update function recursively
walks the difference operators, keeping, dropping, or updating
elements as dictated. In one embodiment, the leftmost existing
element is used, if any, as the "default" value argument to the
function call that is pushed back.
[0127] A number of cases are examined. If there was an insertion at
the beginning of a non-empty list, there was no leftmost element,
wherein the rightmost element (the singleton) is used. If there is
an insertion in an empty list, then update fails to produce a
solution, rather than requiring an explicit default value to be
chosen. In an embodiment, updated functions are also collected, and
at the end they are combined together using the built-in merge
operation. There are many other reasonable ways to define update
for this lens, and by exposing this choice, users may provide
custom implementations as appropriate to suit their own
purposes.
HTML-to-String Lens
[0128] A lens for parsing an HTML string to a list of encoded HTML
nodes may be included in some embodiments. The HTML-to-String lens
illustrates the challenge of tolerating a variety of
potentially-malformed documents, and carefully tracking whitespace,
quotation marks, and other characters that are not stored in the
resulting DOM, all of which are needed to respect the formatting
conventions of the program. Because these characters are respected,
in some embodiments, users may copy-and-paste HTML strings into
long string literals for convenience.
`fancylf` Lens
[0129] In some embodiments, such as when evaluating programs whose
structure and control-flow are mostly correct, guard expressions
may not need to be changed. However, guard expression modification
can be defined with lenses, in other embodiments. For example, the
`fancylf` function below employs a lens to augment the built-in
approach for updating if-expressions (pushing values back to the
same branch) with the ability to change the guard expression. If
the original guard c evaluates to True and the original else branch
a evaluates to the updated value v, then pushing False back to c
constitutes a second solution, called `updateGuard`. The treatment
for when c evaluates to False is analogous:
TABLE-US-00001 fancyIf cond thn els = Update.applyLens {apply (c,
t, e) = if c then t else e , update {input=(c,t,e), outputNew=v} =
let updateSameBranch = if c then (c, v, e) else (c, t, v) in let
updateGuard = if (c && e == v) || (not c && t == v)
then [(not c, t, e)] else [ ] in { values =
updateSameBranch::updateGuard } } (cond, thn, els)
[0130] In another embodiment, the value may be pushed back to the
other branch even if it does not already evaluate to the desired
value v. Such variations can be implemented easily using the
present techniques.
[0131] It should be appreciated that many other examples of lenses
are possible. For example, a lens may be defined for appending
lists, which generates multiple candidate solutions when inserting
elements at the "split" between the two input lists. An evaluation
update for concatenating strings may do the same. Several custom
update functions helpful for achieving a variety of desirable
interactions for bidirectional functional documents are described
with respect to FIG. 4.
Evaluation and Benchmarks
[0132] FIG. 4 depicts a table of benchmark data related to the
execution of a plurality of example programs. In practice, many
diverse examples comprising hundreds of lines of code have been
created using a programming system for developing and editing HTML
documents and web applications, based on the techniques discussed
above. The example programs are designed to facilitate a variety
useful direct manipulation interactions enabled through
bidirectional evaluation, and they demonstrate that a variety of
interactive documents and applications--such as web pages,
Markdown-to-HTML translators, a -to-Html editor, and scalable
recipe editors--can be programmed using the techniques described
herein in a way that allows direct manipulation changes to
propagate automatically back to the program.
[0133] Moreover, these evaluations demonstrate that the present
techniques may synthesize program repairs very quickly (e.g.,
within 0 to 2 seconds) in full-featured interactive settings. These
examples also demonstrate how a variety of HTML documents and
applications can be developed and edited interactively using the
present techniques, thereby mitigating or even eliminating the
tedious edit-run-view cycle drawbacks discussed above that plague
traditional programming and/or software development environments.
Although the examples discussed herein relate to HTML and web-based
programming, the present techniques allow programmers and end users
to combine programming with direct manipulation in any programming
domain/paradigm. Specific evaluation examples are discussed below,
in addition to the direct manipulation interactions the examples
enable.
Evaluation Examples
States Table
[0134] The States Table A benchmark in FIG. 4 includes direct
manipulation text and DOM edits, and the States Table B benchmark
corresponds to interactions with custom buttons. Bidirectional
evaluation via DOM editing is discussed below, with respect to FIG.
5d and FIG. 5i. In some embodiments, custom user interface features
may be constructed by: [0135] 1. defining a lens that, in the
forward direction, attaches extra "state" to some data and, in the
backwards direction, refers to the updated state to determine how
to update the data; and [0136] 2. (ii) exporting HTML elements that
store the state and handling events in some JavaScript code
generated as strings according to the above syntax that map browser
events to edits to the state.
Scalable Recipe Editor
[0137] A culinary recipe may be presented in such a way that
ingredient amounts can be scaled easily with respect to a desired
number of servings. The source of the recipe is stored as a string
containing HTML code. There, every occurrence of "multdivby(p,q)"
is first replaced using regexes, the implementation of which was
discussed above, by the number (p/q)*servings, where servings is
defined for the entire recipe. The resulting string is then
evaluated by a String-to-HTML lens. To insert the quantity "5 eggs"
proportional to a current number of servings of 10, users can
simply enter "_5_egg" in the output, and the "_5_" is replaced by
custom lenses to "multdivby(5,10)" in the source text. Similarly,
inserting "_5s_" inserts a conditional plural in "s". Because all
proportional quantities are connected to servings through
invertible arithmetic operations, the user can edit any of the
values as desired--e.g., to scale the recipe to make 32 servings,
or to find how many servings can be made with 12 eggs--all others
are updated accordingly.
Mini Markdown-to-HTML Editor
[0138] In this example, a regular expression-based program was
created to convert Markdown strings to HTML strings. Using built-in
support for text updates, the present techniques can, for example,
demarcate a string in the output text with underscores that get
pushed back to the Markdown string. Then, after evaluation, the
text is italicized due to <em> tags inserted by regular
expression transformations. For more advanced functionality, lenses
were implemented to translate Markdown headers (#, ##, etc.) to
their HTML counterparts (<h1>, <h2>, etc.), translate
unordered and ordered list elements (e.g. <ii> to either "*
A" or "1. A"), and translate <div> and <br> elements to
the correct number of newlines.
ADDITIONAL EXAMPLES
[0139] The remaining rows in FIG. 4 correspond to: Budgeting is the
computation of a budget for which, the result of the expression
(income-expenses) is updated to be zero, causes the program update
to include all choices for changing the values of lunch,
registration, and other expenses. Model-View-Controller
demonstrates an interactive page that manipulates the state of the
application with buttons and user-defined functions. Mini
Linked-Text Editor, wherein users can create links ("variables")
between portions of text so that updating any clone updates them
all. Translation Doc is a instruction manual in two languages where
user can change the language, add and clone translations. Dixit is
a scoresheet for the game to ask for bets and compute scores. in
Html allows the user to modify the output of an editable
lightweight source file that includes \newcommand, sections,
references, labels, and unlimited equations. Interestingly, lenses
enable the propagation of reference numbers as an updated reference
name, to propagate HTML bold and italic markers to their
counterparts, and to escape backslashes if they are entered from
the output.
Performance of Update Algorithm
Methodology
[0140] To validate that the program update algorithm is fast enough
to support an interactive direct manipulation workflow, the running
time for several benchmarks was measured. Each benchmark in FIG. 4
reflects a summary of the running time of an example program and an
interactive editing session. Specifically, the "LOC" column depicts
the number of lines of code for the initial program and "Eval"
depicts the running time (in milliseconds) averaged over 10 trials.
For each example program, a series of direct manipulation edits and
program updates were performed, and each editing/update session
produced a sequence of all calls to the update algorithm. "#Upd"
depicts the number of calls to the program update algorithm during
the session.
[0141] An offline performance evaluation was performed by replaying
the sequence of updates in each session For each call to program
update, the time to compute solutions with an unoptimized version
of the update algorithm was measured, wherein the unoptimized
version ("Unopt") included Optimizations 1 and 2 described above,
and a "fully-optimized" version ("Opt"), which also included
Optimization 3 regarding edit differences.
[0142] Without Optimizations 1 and 2, the algorithm may run out of
stack or heap stack on some benchmarks. As noted, each of these
calls was performed 10 times, and the running times in the last
three columns of FIG. 4 are averages over the 10 trials. The
"Slowest Upd" column depicts the (average) running time of the
slowest call to update (using the "Opt" algorithm) for the given
session, "Fastest Upd" depicts the fastest, and "Average Upd"
depicts the (average) running time off all calls in the
session.
Results
[0143] The data in FIG. 4 suggest three main observations. First,
that edit difference optimization is crucial for performance. The
"Average Upd" column of the last row are averages across calls to
update, as opposed to averages of the rows above. Across all 92
calls to update across all benchmarks, the average running time for
the fully-optimized algorithm is 723 ms. Thus, the use of edit
differences, rather than plain values, is crucial for making
evaluation update feasible in the setting.
[0144] Second, that performance of evaluation update is comparable
to evaluation. The average evaluation update time (723 ms) is
nearly the same as the average evaluation time (833 ms). Because
the evaluation update algorithm performs much the same work as
evaluation, this suggests that the optimizations described herein
achieve most opportunities for speedup. Further gains, both for
evaluation and update, are likely to result from optimizing the
interpreter--or compiling to "native" JavaScript code--as opposed
to additional optimizations of the current approach. In some
embodiments, updating the interpreter and/or compiling the code to
native JavaScript, or another compiled/intermediate format, may be
performed to achieve additional speedups.
[0145] Third, there is little ambiguity in the example
interactions. Across all 92 calls to update across all benchmarks,
the average number of solutions is 1.18. The degree of ambiguity
for program repairs is heavily dependent on the programs and
interactions under consideration. However, the example programs and
interactions demonstrate a variety of useful and realistic
scenarios for interactive editing. Together with the data, this
suggests that novice and experienced users/programmers alike can
develop programs in such a way that direct manipulation edits lead
to the desirable repairs without an overwhelming amount of
ambiguity.
Example Bidirectional Evaluation with Direct Manipulation
Environment
[0146] In many embodiments, a user (e.g., a web developer or other
programmer, or a nonprogrammer) may want to implement an
interactive document, using a programming environment that allows
the user to edit the input source code that generates output, and
the output directly, using the methods and systems described above.
For example, a user may want to create a computer program to
generate an HTML table wherein the rows correspond to each of the
United States of America, along with the respective capital cities
of each State. In general, source code is defined herein to mean
the sequence of characters, whitespace, and symbols used to compose
a computer program. However, in some embodiments, "source code" may
include data, serialized values, complex data objects, images,
video files, symbolic expressions, abstract syntax trees, and/or
other electronic objects capable of being evaluated by a
computer.
[0147] Using the present techniques, the user may begin by writing
a computer program in a computer language, such as the language
described in the above discussion, to generate output. The initial
programming effort required to encode all intended data and
presentation constraints is similar to when using traditional
text-based programming environments. That is, the user may write
input source code as she normally would. After writing the input
source code, however, in a significant departure from traditional
programming activities, the programming environment allows the user
to: [0148] 1. edit the data and design parameters through direct
manipulation of the output; and [0149] 2. add elements to the
output through a custom, library-defined user interface.
[0150] The programming environment may synthesize program repairs
based on the user's interactions with the output, thereby
obviating/mitigating the need for the user to return to the input
source code, and eliminating/reducing the tedious edit-run-view
cycle common to traditional programming environments. It should be
appreciated that although the following description includes
examples of HTML generation, any suitable output format may be used
(e.g., JavaScript, SVG, a domain-specific language, a visualization
library, etc.).
[0151] The following includes a description of an example GUI
implementation for bidirectional evaluation for programs. The GUI
provides a lightweight mechanism for previewing and choosing a
solution when there is ambiguity, which may be inherent in some
cases while using a general-purpose language. However, it should be
appreciated that the present techniques are applicable and may be
used in other technical fields and in other programming
paradigms/domains. For example, the present techniques may be used
for interactive programming when creating applications relating to
the trading of financial instruments, to medical data management,
to database systems (e.g., relational and key-value store
databases), in data science, and so on.
Initial Prototype
[0152] FIG. 5a depicts a program source code written by a user to
generate an initial prototype. The program source code may include
string literals (e.g. "California") and strings (e.g.
"California"). Lines 1-8 of the program source code in FIG. 5a
define the data for an HTML table, states. Each element of states
is a three-element list, containing a state name, two-letter
abbreviation, and capital city. states may be a list of lists, and
it may be partially or completely computed from previous
variables.
[0153] In the program source code of the initial prototype, the
data is incomplete. Unknown abbreviations are marked with question
marks (e.g., "AL?" on lines 1-2), whereas undefined capital cities
remain empty strings (i.e. " " on lines 4-8). The state of the
program source code reflects a common practice of developers,
wherein data is left temporarily incomplete while the rendering
portion of the program source code, sometimes known as
"scaffolding," is written. In FIG. 5a, the main definition,
starting on line 10, generates the output HTML table.
[0154] First, the program source code produces two output columns:
one for the state name (e.g., "Alabama"), and one for its
respective capital city, concatenated with the state abbreviation
(e.g., "Montgomery, Ala."). The headers definition at line 11
contains text for the header row, and the rows definition in lines
12-15 contains the text to display in subsequent rows by mapping
each three-element list [state, abbrev, cap] in states to the
two-element list [state, cap+","+abbrev].
[0155] The headerRow definition in lines 18-20 uses library
functions Html.tr and Html.th to generate table row and header
elements, respectively, for the top of the output HTML table. These
Html functions take three arguments: a list of HTML style
attributes, a list of additional HTML attributes, and a list of
HTML child nodes. The Html functions produce encodings of HTML
values to be rendered. For example, the headerRow definition may
generated an intermediate expression, according to the syntax and
semantics discussed above:
TABLE-US-00002 ["tr", [ ], [["th", [["style", [["padding",
"3px"]]]], [["TEXT", "State"]]], ["th", [["style", [["padding",
"3px"]]]], [["TEXT", "Capital"]]]]]
[0156] which may then be translated to the following HTML
element:
TABLE-US-00003 <tr> <th style=''padding:
3px;''>State</th> <th style=''padding:
3px;''>Capital</th> </tr>
[0157] The program source code may also include zebra-striping
code, for improving the readability of the output. The stateRows
definition on lines 22-33 generates the remaining rows of the
table. The colors list at line 23 defines two initial colors,
"lightgray" and "white". The expression at line 25 chooses one of
these colors based on the parity of row index i, as i is received
as a parameter from the List.indexedMap library function. The
columns definition in lines 26-29 places the text for each state
and its capital city--in a two-element list row--inside Html.td
elements, which comprise a row built from the Html.tr expression at
line 31. For example, for the first row, the columns expression is
evaluated and then translated to the following HTML elements:
TABLE-US-00004 [''tr'', [ ], [[''td'', [[''style'', [...,
[''background-color'', ''lightgray'']]]], [[''TEXT'',
''Alabama'']]], [''td'', [[''style'', [..., [''background-color'',
''lightgray'']]]], [[''TEXT'', ''? AL?'']]]]] [''tr'', [ ],
[[''td'', [[''style'', [..., [''background-color'', ''white'']]]],
[[''TEXT'', ''Alaska'']]], [''td'', [[''style'', [...,
[''background-color'', ''white'']]]], [[''TEXT'', ''?
AK?'']]]]]
[0158] These nested lists are translated to the HTML elements:
TABLE-US-00005 <tr> <td style=''padding: 3px;
background-color: lightgray;''>Alabama</th> <td
style=''padding: 3px; background-color: lightgray;''>Montgomery,
AL?</th> </tr> <tr> <td style=''padding: 3px;
background-color: white;''>Alaska</th> <td
style=''padding: 3px; background-color: white;''>Juneau,
AK?</th> </tr>
[0159] Lastly, the expression at line 35 builds the overall
Html.table element comprising headerRows and stateRows. The output
program source code value is translated to HTML and rendered
graphically in the right half of the programming environment, as
depicted in FIG. 5a. Although the example depicted includes HTML
output, and rendering in a particular region of a GUI, the output
may be of another form (e.g., Markdown), and may be rendered in any
suitable location, including in a file, or via a network to a
remote computing device.
Direct Manipulation of Output Text
[0160] In some embodiments, a user who has encoded the intended
programmatic relationships for a data set and an output design of
that data set may next want to correct the missing data (e.g., the
data missing from lines 2-8 of FIG. 5a). As noted above, in a
significant departure from typical programming practices, the
present techniques allow programming environments to be created
which allow the user to edit text directly in the graphical user
interface that displays the output (the right half of the
editor).
Computing and Displaying Program Updates
[0161] In some embodiments, a user may interact with the output to
produce changes to the program source code, and the user may be
provided with an indication of what the resulting changes are. For
example, FIG. 5b depicts an example of how a user may edit the data
in the program source code depicted in FIG. 5a through the
graphical user interface, including a depiction the program
environment state after the following sequence of user actions.
[0162] First, in the first state row of the output table, the user
deletes the question mark after "AL" in the string ", AL?". Next,
in the second row, the user replaces the string "AL?" with "AK". As
soon as the user begins editing the output table (or due to a
user's explicit selection, in some embodiments), the programming
environment may detect that the program output is no longer
synchronized with the program. As a result, the programming
environment highlights the source code input box on the left side
of the programming environment with a red border and displays a
pop-up window including a menu item labeled Update Program.
[0163] When the user hovers over Update Program, the programming
environment runs an evaluation update algorithm to synthesize a
repaired program that, when re-evaluated, generates the same result
as the directly manipulated output. The update algorithm may
proceed according to the principles described above. In the
depicted embodiment, the algorithm computes one solution that,
along with an option for reverting the changes, is displayed in a
nested graphical user interface menu to the right of Update
Program. It should be appreciated that the graphical user interface
aspects of the present techniques may be implemented using any
suitable software development environment (e.g., using a desktop
software development kit, a mobile software development kit, via
web programming frameworks/libraries, etc.). A text-only output
encoding may also be targeted, such as curses.
[0164] FIG. 5b captures the editor state when the user hovers over
the first item in the nested menu, at which point the programming
environment displays a preview of the updated program (resp.
output) directly in the left (resp. right) pane. The caption [0165]
"L2 Removed [?] L3 Replaced [L?] by [K]" summarizes the string
differences, in lines 2 and 3, between the original and updated
program text. These string differences are highlighted in red and
orange in the code box to further help communicate the proposed
changes to the user. In this case, the new program matches the
user's expectations, so the user clicks the menu item (not shown in
the screenshot) to confirm the update, returning the program and
output to a synchronized state.
[0166] In general, it should be appreciated that any suitable means
of communicating differences to users may be used, and that the
user's confirmation and/or rejection of the changes may be
received/collected via any suitable means (e.g., the click of a
mouse, a press of a touch screen, etc.). Having the ability to
accept a user indication before making a change to the program
source code is an important facility, because edits to the output
may lead to ambiguous changes, with respect to the original source
code. In some embodiments, a first display and a second display of
a user may be different physical devices, or a single physical
device of a user. For example, the user may have a desktop with
multi-head computer monitors, or a single computer monitor.
Original program source code and/or program output may be displayed
in any display of the user. Updated program source code and
modified program output may be displayed in any display of the
user. In some embodiments the first display of the user and the
second display of the user are the same device.
Ambiguity
[0167] FIG. 5c depicts a change that leads to plural/ambiguous
solutions. For example, in the third row, the user replaces ", AR?"
with "Phoenix, Ariz.". The change causes the Update Program menu to
be displayed, and when the user hovers over the menu, two solutions
are caused to be displayed, in addition to the option to revert the
changes. FIG. 5c captures the editor state when the second solution
is hovered. In the example, both solutions are valid because each
replaces "AR?" on line 4 with "AZ", as desired, but the second
solution inserts "Phoenix" as a prefix to the "," separator string
used in the concatenation on line 14.
[0168] By viewing the preview of the output, with "Phoenix"
appearing in all rows, the user quickly determines that this
change, though consistent with the output edit, is undesirable. In
this way, the menu with previews represents a lightweight yet
efficient way for the user to disambiguate between multiple valid
updates. The user then hovers over the menu and selects the first
option (not shown in the screenshot).
[0169] The present techniques facilitate the avoidance of
ambiguity. For example, the user may edit the input source code to
wrap the string "","" in a call to Update.freeze (not shown), which
instructs the programming environment never to change this
expression when computing program updates. In this way, the
separator string at line 14 will remain constant. The user may fill
in missing data for the remaining rows directly in the output pane.
Having frozen the separator string already, none of these changes
lead to ambiguity. In some embodiments, additional freeze operators
may be introduced. For example, Update.expressionFreeze may not
prevent a new value from being pushed back to an expression, and
may ensure that the expression stays the same and that only the
variables' values may change. Update.freezeLeft and
Update.freezeRight may prevent insertions to, respectively, the
beginning and end of output strings.
Browser Conveniences for Navigating Output Text
[0170] During the foregoing interactions in the programming
environment, the user benefits from text-editing features built-in
to the browser--using the Tab key to advance to subsequent columns
and rows, and arrow keys to navigate the text cursor within the
selected cell--which make it yet more convenient to specify these
changes in the graphical user interface rather than in the source
code editor.
Programming Environment: Direct Manipulation Programming for
HTML
[0171] The last major aspect of the programming environment is the
user interface for updating output values and interacting with the
program update algorithm. Below, several different direct
manipulation value editors are described. Regardless of which value
editor is used to make changes, the connection to the update
algorithm may proceed as described in "Computing and Displaying
Program Updates," "Ambiguity," and "Automatic Synchronization".
[0172] Multiple types of user interfaces may be implemented for
manipulating output, depending on the embodiment. The first mode is
a Graphical User Interface, which allows the user to make edits
directly in the HTML-rendered output. Some embodiments support
text-based editing. For example, in a translation of HTML text
nodes, a "contenteditable" attribute may be added to allow changes
to the text. In some embodiments, key events (e.g., keypress
events) are received and processed. For example, Ctrl+B may cause
an update to bold text. In some embodiments, direct manipulation
widgets for common properties of other kinds of elements, such as
color, position, size, padding, etc. are available. A second mode
includes a Text Interface, which allows the user to make edits to
the output value rendered as a string. The text interface allows
the string to be rendered either as "raw" HTML or in the syntax
described above. The final mode integrates with the built-in DOM
Inspector provided by modern web browsers. The features provided by
the browser allow users to, for example, select DOM
elements--either by right-clicking or by navigating in a separate
view of the DOM tree--and then use built-in text- and GUI-based
panels for adding, removing, and editing elements and their
attributes.
Direct Manipulation with DOM Inspector
[0173] Continuing with the above example, having corrected the data
in the table, the user may next wish to experiment with different
styles. The direct manipulation output pane in the depicted
programming environment embodiment provides direct manipulation
only for text content (as in the interactions above). However, it
should be appreciated that some embodiments allow the developer to
use the existing Developer Tools provided by modern browsers for
inspecting and modifying arbitrary elements and attributes in the
DOM (i.e. the HTML output of the program). In an embodiment,
changes to the DOM may be used to trigger the program update
algorithm.
Browser Conveniences for Editing Styles
[0174] Built-in browser functionality may have synergies with the
programming environment. For example, a user wants to try out
different colors for alternating rows, to replace the colors at
line 23 in FIG. 5a. FIG. 5d depicts an example of affecting such
changes in the programming environment. First, the user may
right-click the "Hartford, Conn." cell and select Inspect from the
browser's pop-up menu. Of course, the precise mechanism by which
the user accesses a developer tools panel may vary from browser to
browser. As a result, a Developer Tools pane appears at the bottom
of editor (as depicted), with the selected cell in focus in the DOM
Element Inspector. The rightmost panel may provide a Styles Editor,
which the developer can use to change the background-color from the
initial lightgray color, by adding, editing, and/or removing
properties in the Cascading Style Sheet (CSS) of the HTML document
in the right hand side of the programming environment.
[0175] The user may open the DOM inspector and select one of the
cells colored "lightgray" cells in the table (using the Inspect
panel in Firefox browser or the Elements panel in Chrome browser,
or by right-clicking directly on the output element in the right
half of the programming environment). A side panel in the browser
Developer Tools pane lists all of the style attributes for that
cell, one of which is the background-color: lightgray property
generated by the program. The user starts typing ye and, then,
using the built-in conveniences provided by the Styles Editor for
changing color values (e.g., a dropdown menu of related colors,
equipped with tab completion and previews) decides to try the color
yellow.
[0176] As with the text changes described above, the programming
environment may detect that the output is no longer synchronized
with the program source code, and based on the detection, may
trigger the update algorithm, and displays the Update Program menu.
FIG. 5d captures the editor state as the user hovers over the
single solution, which replaces "lightgray" at line 23 with
"yellow" to reconcile the change. In the output of the updated
program source code, the color of all cells in alternating rows are
changed (not only the one cell directly manipulated). Notably, the
present technique has allowed the user to modify both the source
code of the program, and other rendered output parts of the source
code, without directly interacting with either.
Automatic Synchronization
[0177] In some embodiments, the programming environment may
facilitate automatic synchronization between the program source
code and the rendered output without the user needing to confirm
the updates. For example, the user may want to experiment with
colors, but manually hovering and clicking the Update Program menu
will be tedious when trying several options. So, the developer may
click the button labeled Auto Sync in the right toolbar, which
toggles the editor into a mode that performs automatic updates.
Specifically, whenever the output is changed--either directly in
the graphical user interface and/or through the DOM Inspector--the
program update algorithm is automatically run after a configurable
delay (e.g., 100 ms). When there is a single solution, it is
applied automatically, without requiring the user to hover and
select the update through the menu.
[0178] Thus, the developer can try several colors in the DOM
Inspector in rapid fashion, viewing how the change propagates
immediately to the entire table.
Small Updates
[0179] As noted above, the user can add HTML elements/attributes
via the DOM. For example, to continue with the above example, the
user may wish to add a background color to headerRow, whose styles
list on line 19 does not include a color.
[0180] FIG. 5e depicts a menu displayed when the user selects a td
element. After selecting the first column of the header row in the
browser DOM Inspector (either by right-clicking, or using the
browser's built-in Inspect cursor), the user, again, uses the
Styles Editor, as depicted in FIG. 5f. The Styles Editor provides
an easy way (with a mouse click or Enter key press) to add a new
attribute. The user adds a new background-color attribute set to
the value orange, as depicted in FIG. 5f, and the corresponding
program update adds the pair [ "background-color", "orange"] to the
styles list on line 19. The resulting updated output HTML and menu
showing the changes and option the user to revert the changes are
depicted in FIG. 5g.
[0181] Therefore, unlike the local updates described above, wherein
constant literals in the program source code were replaced with new
ones, the user has succeeded in performing a structural update,
which alters the structure of the abstract syntax tree.
Specifically, the user has added a "background-color" to the DOM,
where none previously existed. Some embodiments may transcend local
and pretty local updates. Using lenses and pushing back closures,
the entire function body may be changed to, for example, replace a
function f with another function f'. An API may be exposed for
editing the closure, which may allow the user to develop tools to
customize the body of the functions.
[0182] As described above with respect to FIG. 2c, such structural
updates are referred to pretty local because the only change to the
structure is inserting a new literal at a leaf of the AST (i.e.,
inside another list literal). The program update algorithm in the
programming environment may produce only local and pretty local
changes to the program, a restriction that nevertheless results in
a useful set of "small" changes to the original program.
Direct Manipulation with Custom User Interfaces
[0183] Throughout the direct manipulation interaction examples
described thus far, the user has leveraged GUI features provided by
the programming environment and/or existing browsers to edit the
content and styles of existing rows in the table. It should be
appreciated that performing tasks that are not provided by the
programming environment and/or another environment (e.g., a web
browser), are also supported by the present techniques. For
example, a row with columns "Delaware" and "Dover DE" may be added
to the bottom of the output HTML table. Programmatically, this
change corresponds to adding a new three-element list ["Delaware",
"DE", "Dover"] to the end of the states list.
[0184] The developer could directly manipulate the output HTML by
copying the last <tr> to a new row, and changing the content
of the row. However, as described above, the program update
algorithm cannot reconcile such changes with the original program,
because such a reconciliation so would require simultaneous
reasoning about inserting elements into lists (in this example,
states) that are being destroyed (in this example, by List.map) and
whose elements are being transformed by some function (in this
case, the anonymous function on line 14 of FIG. 5i). In an
embodiment, the programming environment includes logic for
detecting changes that modify the library, and providing the user
with an error message explaining that such changes are not
permitted.
User-Defined Program Updates with Lenses
[0185] When the built-in program update algorithm does not
facilitate the direct manipulation interactions desired for a
particular task, the programming environment provides users (or
library writers) with the ability to define a custom lens that
augments a "bare" function with a second update function that
defines the "reverse semantics" for the bare function. For example,
to continue the above example, the user can use lenses to define a
module called TableWithButtons--which performs more advanced
evaluation update than for basic List.map--to serve as a drop-in
replacement for the basic table-constructing functions in the Html
library. FIG. 5h depicts an upgraded code library implementing the
TableWithButtons, according to an embodiment.
[0186] FIG. 5i depicts a graphical example, using the more
sophisticated code library, of clicking a button (labeled "+")
causing a new row to be added at the clicked position. For example,
assume that the user clicks the button next to the "Connecticut"
row, hovers Update Program, and then hovers the single solution (as
shown in the screenshot). Based on this series of interactions, the
resulting program adds a placeholder/blank row at line 9 in the
states list, which can later be filled in through the basic direct
manipulation text interactions as before. Thus, by using lenses to
augment the functionality of the built-in program update algorithm,
users and library writers can implement custom user interface
features for manipulating the particular bidirectional functional
documents under construction. Of course, it should be appreciated
that structural elements other than HTML table rows may be added.
In some embodiments, the added elements may be other than HTML
elements (e.g., any suitable complex data objects). The types of
structural elements that may be added, edited, and removed are
often determined by the which datatypes form the currency of a
particular programming environment.
Library Design for HTML Programming
[0187] The main definition of a program may compute an HTML value,
using a list-based encoding of HTML elements. A text element may be
represented by a two-element list ["TEXT", s] and a non-text
element by a three-element list [tag, attributes, children], where
tag is an HTML tag (e.g. "div", "span", "h1", etc.), attributes is
a list of string-value pairs (rather, two-element lists), and
children is a list of HTML elements. In some embodiments, a
list-based encoding that includes explicit datatypes may be used,
in addition to a small Html library to make programming with this
encoding more convenient.
Co-Design for Pretty Local Updates
[0188] FIG. 5j depicts a Html module that provides helper functions
for several common tags. These functions may take a number of
arguments. For example, three arguments may be provided: a list of
HTML "style" attributes, a list of non-style attributes, and a list
of children. The Html library functions are used, above, in Lines
20, 28, 31, and 35 of FIG. 5a depict example calls to Html. The
choice to provide style and non-style attributes separately is for
clarity--to avoid having the "style" attribute list be nested
within another list. However, the choice may be altered in some
embodiments. The choice for the "default" library functions to take
attribute lists as arguments--even when they are empty--is to
facilitate the addition of styles during subsequent direct
manipulation interactions. But in some embodiments, this behavior
may be modified. In general, calling library functions with literal
arguments is a convention that is established to provide an update
algorithm with a place in the user program, as opposed to the
library implementing the update, to add or remove attributes. This
convention may be useful in some embodiments wherein an update
algorithm makes structural changes to list literals, as discussed
above with respect to pretty local updates.
Additional and Alternate Embodiments
[0189] In some embodiments, a hybrid, demand-driven approach may
used, for large programs where both time and memory are limited
resources, wherein the time and space/memory tradeoffs are
configurable. In particular, the initial evaluation of a program
could proceed without traces, resorting to evaluation update when
output values are changed. Then, when re-evaluating parts of the
program to reconcile the changes, evaluation could record traces
with the expectation that values for those expressions are more
likely to be changed again. The subsequent interactions could then
use trace information (and constraint solving) where available to
avoid re-evaluation. A benefit to incorporating traces and
constraint solving is to enable more precise reasoning than, for
example, the "top-down" approach to inverting arithmetic operations
described with respect to FIG. 2b.
[0190] The methods and systems described herein include two
distinct notions of "bidirectionality." First, all programs are
reversed in a general-purpose language, wherein the techniques in
fact, reverse the language interpreter. That is, in bidirectional
evaluation, arbitrary programs in a general-purpose functional
language may be evaluated "in reverse," by synthesizing program
repairs based on differences between original and desired output
values. The practicality of this approach is demonstrated by the
programming environment discussed above, which represents a new
direct manipulation programming system used to develop a variety of
HTML documents and applications that can be interactively edited
because of bidirectional evaluation.
[0191] Second, the creation of defined lenses for customizing the
behavior of the "backwards interpreter" is facilitated. Unlike
prior work on lenses, and other mechanisms for bidirectional
transformations, the present techniques enable users to write
arbitrary pairs of (well-typed) apply and update functions, wherein
the latter are "hooks" to customize the update algorithm. In
contrast to past approaches, a fundamental goal for the lenses work
is to ensure that the pair of functions satisfies various roundtrip
laws.
[0192] The present techniques allow edits to the output of
arbitrary programs, and such modifications must be supported,
because they arise frequently in the presence of ambiguity (e.g.,
in determining whether a change should be propagated to the
function or data) and concurrent edits (e.g., one user changes a
function, an other changes the data). Given the flexibility enabled
by arbitrary functions, a reversible list map can be defined which
backpropagates changes to the list elements as well as the function
itself. The evaluation update algorithm can, itself, be "lifted" to
user-defined functions and data structures by exposing its
operations in an Application Programming Interface (API).
[0193] This API to define lenses relates to matching lenses, in
which lenses are parameterized over a choice for how to align
subsets of data in the input and output domains. However, in the
present techniques, the built-in update algorithm may use a Diff
operation based on a single heuristic, and this operation is
exposed to user-defined lenses through the diff primitive. In some
embodiments, some prior art approaches to lenses may be integrated
into the present bidirectional evaluation scheme, to provide
mechanisms for varying degrees of reasoning principles and
interaction paradigms, as needed according to various
embodiments.
[0194] In an embodiment, evaluation update may reason about
control-flow choices in order to prune solutions that would deviate
from them, which would enable a stronger correctness property in
situations that require it. In another embodiment, bidirectional
evaluation may be integrated with, for example, type-directed
program synthesis to synthesize "larger" kinds of repairs. In still
other embodiments, exposing expression and value abstract syntax
trees entirely to user-defined update functions (i.e. quote and
unquote) may enable more expressive metaprogramming mechanisms to
customize bidirectional evaluation. Finally, more full-featured
direct manipulation programming systems--for HTML as well as other
domains--may further help to break free from the edit-view-run
cycle of traditional programming environments.
[0195] The examples and results above demonstrate that the present
techniques enable many useful direct manipulation programming
interactions. Nevertheless, there are several technical and
engineering limitations that are addressed in particular
embodiments. For example, a single heuristic is used for
implementing the Diff operator, in some embodiments. Given a list
[a, b], if b is updated to b' and then c is inserted at the
beginning like [a, c,b'], the Diff algorithm aligns lists and end
up concluding that b was updated to c and that b' was inserted at
the end. In other embodiments, alternative alignment algorithms may
be used.
[0196] Furthermore, nested differences are not supported by Diff in
some embodiments. For example, if [x, y, z] is updated to [x, ["b",
[ ], [y]], z], some embodiments may fail (possibly with an
ungraceful exception) because it is assumed that the expression
which produced y should be updated with ["b", [ ]], [y], when in
fact, that expression should be updated based on y and then
propagated upwards. However, in other embodiments, alternative
nested difference algorithms may be used.
[0197] In practical engineering terms, there are situations in
which a DOM listener becomes unsynchronized with the editor state,
and the editor cannot reason about larger structural changes to the
DOM. These limitations have the potential to affect the usability
of some current programming environment implementations, and may
require changes to upstream codebases. In general, such issues are
not fundamental to the techniques described herein, but are
symptomatic of bugs in other systems.
Example Bidirectional Evaluation Computing Environment
[0198] FIG. 6 depicts various aspects of a computing system 600 for
facilitating bidirectional program evaluation, in accordance with
some embodiments. The high-level architecture of the computing
system 600 includes both hardware and software components, as well
as various channels for communicating data between the hardware and
software components. The computing system 600 may include hardware
and software modules that perform methods of bidirectional program
evaluation for purposes of facilitating user programming (e.g., for
creating HTML documents). The modules may be implemented as
computer-readable storage memories containing computer-readable
instructions (i.e., software) for execution by a processor of the
computing system 600.
[0199] The computing system 600 may include a client computing
device 602, a computer network 604, a remote computing device 606,
and a database 608. The client computing device 602 may include a
personal computer, smart phone, laptop, tablet, or other suitable
computing device. The client computing device 602 may include
various hardware components, such a central processing unit (CPU)
602A, a memory 602B, a program module 602C, a network interface
controller 602D, an input device 602E, and a display device 602F.
The CPU 602A may include any number of processors, including one or
more graphics processing unit (GPU). The memory 602B may include a
random-access memory (RAM), a read-only memory (ROM), a hard disk
drive (HDD), a magnetic storage, a flash memory, a solid-state
drive (SSD), and/or one or more other suitable types of volatile or
non-volatile memory. The memory 602B may store, or contain, one or
more program module 602C. The program module 602C may be one or
more computer programs, including computer-readable instructions.
The computer-readable instructions may be stored as program source
code, and may correspond to the program source code depicted in
lines 1-35 of FIG. 5a and lines 18-28 of FIG. 5d, for example. The
computer-readable instructions may also correspond to the output of
such program source code, such as the table of states and
respective capital cities depicted in FIG. 5a.
[0200] The program module 602C may contain a separate set of
instructions that, when executed, cause a graphical user interface
such as the one in the programming environment of FIG. 5a to be
rendered, such that the user can interactively modify the program
source code and/or the output corresponding to the program source
code, and view as changes to either are propagated in both
directions during bidirectional evaluation as described above. The
graphical user interface of the programming environment may include
facilities for opening files, saving files, and editing existing
files. The graphical user interface may also include buttons or
other user interface widgets for accessing certain functionality
with respect to the programming environment, such as toggling Auto
Synchronization, as discussed above. The program module 602C may,
in some cases, include instructions for monitoring the status of a
DOM associated with the programming environment, and for responding
to changes based on detecting events during the monitoring.
Computer-readable instructions stored in the program module 602C
may, when executed, cause information to be sent, received and/or
retrieved via the network interface controller 602D.
[0201] The network interface controller 602D may include one or
more physical networking devices (e.g., an Ethernet device, a
wireless network controller, etc.). The network interface
controller 602D may allow the client computing device 602 to
communicate with other components of the computing system 600 via a
computer network such as the computer network 604. The input device
input device 602E may include one or more peripheral device such as
a detached keyboard or mouse, or an integral device such as a
capacitive touch screen of a portable computing device. The input
device 602E may include a microphone, in some embodiments. The
display device 602F may include one or more suitable display, such
as a computer screen, monitor, capacitive touch screen, television
screen, etc.
[0202] In some embodiments, the client computing device 602 may
connect to other components via a computer network such as the
computer network 604. The computer network 604 may include any
suitable arrangement of wired and/or wireless network(s). The
computer network 604 may include public and/or private networks
(e.g., the Internet and/or a corporate network). In some
embodiments, the computer network 604 may include a local area
network (LAN), wide area network (WAN), metropolitan area network
(MAN), virtual private network (VPN), etc. The client computing
device 602 may connect to any other component of the computing
system 600 via the computer network 604.
[0203] The other components of the computing system 600 may include
one or more remote computing device 606. The remote computing
device 606 may be implemented as one or more hardware devices, and
may be a backend component of the computing system 600. The remote
computing device 606 may include various hardware components, such
as a CPU, a memory, a NIC, an input device, and/or an output device
(not depicted FIG. 6). The CPU may include any number of
processors, possibly including one or more GPUs. The memory may
include a RAM, a ROM, an HDD, a magnetic storage, a flash memory,
an SSD, and/or one or more other suitable types of volatile and/or
non-volatile memory (not depicted FIG. 6). The NIC may include one
or more physical networking devices (e.g., an Ethernet device, a
wireless network adapter, etc.). The NIC may allow the remote
computing device 606 to communicate with other services in
computing system 600 by sending, receiving, and/or retrieving data
via the computer network 604. A user of the computing system 600
may interface with the remote computing device 606 via the input
device and/or display device of the remote computing device
606.
[0204] The remote computing device 606 may include one or more
modules implemented as hardware and/or computer-readable
instructions (e.g., software). For example, the remote computing
device 606 may include an evaluation module for performing
bidirectional evaluation of computer code. In some embodiments, a
evaluation module may also, or alternatively, be located in the
program module 602C of the client computing device 602. The
evaluation module may include instructions for receiving a program
source code, evaluating the program source code to generate an
output, transmitting and/or displaying the output, receiving an
edit to the output, evaluating the output to generate an updated
program source code, and transmitting the program source code to
and other component of the computing system 600 (e.g., to the
client computing device 602). Of course, depending on the
embodiment, the foregoing actions may occur completely in the
client computing device 602. An advantage of using the remote
computing device 606 may be that the remote computing device 606
includes more powerful computational and/or space capabilities than
the client computing device 602, and may this provide more
responsiveness.
[0205] The remote computing device 606 may include a database 608,
which may include a relational database, key-value data storage
system, or other suitable storage device/system. The database 608
may be used to store program source code, modules, and/or lenses.
The database 608 may be used by the bidirectional evaluation update
algorithms for intermediate storage, and for saving logs of
programs. For example, in an embodiment, every change to a program
source code and/or its output through the bidirectional evaluation
update algorithm may cause a copy of both the original program, the
output of the original program, the delta in the updated program
source code (e.g., the code that was changed), and the updated
output to be stored in the database 608. In this way, the user
could later replay the changes that were made to the program source
code and its output over time. In an embodiment, the database 608
may correspond to a source code management application (e.g., a Git
repository). The database 608 may also be used to contain lenses
and/or modules authored by users that are used in conjunction with
the update operations described above.
[0206] In operation, a user (e.g., a computer programmer) may want
to write some source code to produce and output. In some
embodiments, the user may want to edit existing source code. In
some other embodiments, the user may want to edit the output of
existing source code directly, rather than editing the source code.
The user may begin by opening an application in the client
computing device 602. The application correspond to the programming
environment depicted in, for example, FIG. 5a. As discussed above,
the user may begin by opening a saved file, or creating a new file.
The file may already include programming instructions (e.g.,
program source code), to which the user may contribute additional
instructions. The user may then execute the program source code by
interacting with the programming environment. For example, the user
may press or click a "Run" button with a computer mouse, or press a
series of keys on a keyboard to cause the instructions to be
evaluated in a forward direction.
[0207] In some embodiments, forward evaluation may include a
compilation step. The evaluation may cause output to be displayed,
corresponding, in some embodiments, to the evaluation of program
source code written in syntax provided herein, to generate an HTML
output as described with respect to FIG. 5a et seq. The output may
then be displayed in the programming environment.
[0208] Next, the user may modify the output directly, either by
direct interaction with the output as displayed in the programming
environment, and/or via a DOM editor. The modification may include
edits to textual information, as well as the addition of new
structural elements. The type of structural elements that are
permitted to be added may be governed by modules that the user has
created and/or loaded in the programming environment, which may
included lenses. For example, the user may use the TableWithButtons
module to allow additional table row elements to be added, as
described with respect to FIG. 5h. In some embodiments, such
modules may be stored in the program module 602C of client
computing device 602, or in a storage module of remote computing
device 606. The client computing device 602 may retrieve modules
for use in the programming environment.
[0209] Once the output has been modified by the user, the
programming environment may immediately detect that a change has
been made, and may execute a reverse update algorithm, as described
above, to determine how the program source code must be modified in
order to match, or produce, the modified output. As discussed, in
some embodiments, the reconciliation process may run automatically,
either at an interval, or based upon the occurrence of an event
(e.g., a click event). If the update results in one updated program
source code, then the updated program source code may be displayed
in the programming environment (e.g., in an editor window). The
changes that were made may be annotated in the updated program
source code, for example, by the addition of colored regions,
syntax highlighting, or other visual cues, as depicted in the above
examples.
[0210] In some cases, the updated program source code may not be
immediately displayed, and rather, one or more graphical user
interface element (e.g., a popup menu) may be displayed which
depicts a textual representation of the change(s) that the update
algorithm discovered when performing reverse evaluation. For
example, the popup menu may include a message depicting the
removal, replacement, and/or addition of one or more string
characters. Hovering over the popup menu may preview the changes to
the output and/or the program source code displayed in the editor
window, and the popup menu may also include an option to revert the
program to its original state.
[0211] In some cases, more than one valid program source code may
be mapped to the updated output by the update algorithm. In those
cases, the popup menu (or another graphical user interface
facility) may depict each of the possible changes to the original
program source code, and the user may choose from among them. Once
the user makes a selection, the program source code may be
immediately updated with the changes corresponding to the user's
selection.
[0212] In some embodiments, a heuristic may be applied to
automatically select one of a set of ambiguous edits, without
requiring the user's intervention. Such a selection may be based
on, for example, selecting the edit that affects the fewest number
of characters in the original program source code.
Example Method for Bidirectional Evaluation
[0213] FIG. 7 depicts an example method 700 for performing
bidirectional programming, according to an embodiment. The method
700 may include receiving original program source code (block 702).
For example, the original program source code may be opened by the
user in a programming environment in the client computing device
602 of FIG. 6. The programming environment may correspond to that
depicted in, for example, FIG. 5a and FIG. 6. In an embodiment, the
original program source code may programming include instructions
in a programming language corresponding to the syntax discussed
with respect to FIG. 1a.
[0214] In general, the program source code may be received and/or
retrieved from any computer via a computer network, including from
the remote computing device 606 of FIG. 6 via the computer network
604, and/or from a computer memory (e.g., from the memory 602B).
Once received/retrieved, a file containing the program source code
may be read, and the contents of that file displayed in a display
device (e.g., the display device 602F of FIG. 6). The contents may
be statically parsed, for example, to syntax-highlight the code for
usability purposes.
[0215] Once the original program source code is displayed, it may
be evaluated to generate a program output (block 704). The
evaluation may be performed by the programming environment and/or
by a separate component. For example, in some cases, an evaluation
module (e.g., program module 602C) of the programming environment
may read the program source code and input it into an interpreter.
The interpreter may evaluate the program source code, using the
forward half of the bidirectional evaluation techniques discussed
above, and may generate an output corresponding to the result of
the evaluation. The result of the evaluation may be a program
output, and may be composed of any electronic data (e.g., strings,
numbers, data structures, objects, records, expressions, etc.). In
some embodiments, evaluating the code may be performed by a remote
computing system, such as remote computing device 606. There, for
example, evaluation may include transmitting the program source
code via the computer network 604 to the remote computing device
606, wherein a module local to the remote computing device 606
including a language interpreter, compiler, and/or runtime may
evaluate the program source code.
[0216] The program output generated at block 704 may be displayed
in a display device of the user (e.g., the display device 602F of
FIG. 6) (block 706). In some embodiments, the program output may be
continuously re-displayed, each time the user makes any change to
the program output. This may serve the important function, in some
embodiments, of notifying the user that the change the user has
made to the output has been accepted/persisted in the programming
environment. However, in some embodiments, the output of the
program source code may be transmitted to another local process
(e.g., a different program executing in the same memory/address
space) or a remote process (e.g., a different program executing in
another computer). In some embodiments, the output may not be
displayed, and may only be stored for later analysis, such as in
database 608 of FIG. 6. The programming environment may include the
ability to render the output of the evaluation. For example, if the
output is display code, such as CSS and/or HTML, then the
programming environment may use a browser toolkit (e.g., WebKit) to
immediately render and display the output. In some embodiments, the
evaluation of the program source code in method 700 may include
injecting JavaScript or other ancillary code into the evaluation
output. The JavaScript code may include, for example, event
handlers for detecting changes to the output code.
[0217] Once the output of the program source code is displayed, a
user may interact with the output directly, and may transmit
indications corresponding to modifying the program output to the
programming environment (block 708). The user who programmed the
original program source code may or may not be the same user who
interacts with the output. The interaction may take the form of a
user clicking on the output, with a mouse pointer, and/or cursor.
The user may access the output via a keyboard (e.g., a Tab key of a
keyboard) or any other key(s). The user may edit existing elements
in the output, using graphical user interface elements that are
built-in to the programming environment (e.g., widgets, a DOM
inspector, a contextual menu, an input field, etc.). The user may
also edit add, remove, and/or create new elements (i.e., make
structural changes to the output) as discussed with respect to FIG.
5i.
[0218] After any indication(s) of the user corresponding to
modifying the program input are received, the modified program
output may be evaluated "in reverse" as discussed above. The result
of modifying the program output may be an updated program source
code, wherein the result of evaluating the program source code in
the forward direction may output the modified program output (block
710). Evaluating the program in reverse may be used to determine
and/or reconcile differences between the original program source
code and the updated program source code. For example, as discussed
in FIG. 5c, the programming environment may determine that
particular line numbers (L2 and L3) are affected by the modified
program output, and the specifics of the modifications may be
determined (e.g., that a first group of one or more characters has
been replaced by a second group of one or more characters).
[0219] It should be appreciated that after the modified program
output is evaluated to generate updated program source code, the
updated program source code and/or the modified program output may
be displayed in a display device and/or graphical user interface.
In some embodiments, the display device may correspond to the
display at block 706. For example, a user may have two computer
monitors, and the program source code may be displayed on one
display, while the program output is displayed in another. In other
embodiments, the first and second displays may be coupled to
different computers, respectively. For example, the program source
code may be displayed in a client (e.g., client computing device
602) and/or a server (e.g., remote computing device 606). The
updated program source code may be transmitted, in some
embodiments, over a network such as computer network 604.
[0220] Reverse evaluation may enable the programming environment to
highlight more than one line of code in the updated program source
code, to indicate the potential consequences of applying ambiguity
in the updated program source code to the original program source
code. The user may have the option of reviewing each of a plurality
of ambiguous updates, wherein the user's review of each one causes
the each updated program source code in the plurality of ambiguous
updates to be displayed in realtime, thereby allowing the user to
quickly determine which of the ambiguous updated program source
code is intended/preferred. By allowing the user to directly
manipulate the output of programs, without requiring the user to
resort to reasoning about the original program source code, and by
automatically generating updated program source code reflecting the
user's desired edits to the output, the present techniques greatly
improve the efficiency of software development.
Example Bidirectional Evaluation Language Embodiments
[0221] As noted above, the conventional approaches for writing
inverse evaluators, or "unevaluators" include serious shortcomings.
Thus, the present techniques provide methods and systems for
applying Bidirectional Evaluation to multiple languages (e.g., PHP,
Python, JavaScript, etc.).
Difference Language
[0222] In an embodiment, the present techniques enable
multi-language support by 1) storing the final environment so that
intermediate results can be cached and not recomputed on update and
2) detecting complex function applications (e.g., f(h(g(a)))),
rewriting those applications to let-in expressions with temporary
variables that may benefit from the caching. However, the foregoing
two-step process may not apply to some semantics (e.g., nested
lets, overridden variables/private variables in records, local
functions and computed functions), and may lead to unoptimized
behaviors.
[0223] Attempts to address a gap caused by rewriting by maintaining
a "difference" (e.g., a representation of how a new value differs
from a previous value) along with a back-propagated new value may
also be error-prone. It should be appreciated that back-propagation
of differences may be essential (e.g., to avoid exponential
complexity of merging environments, when each value of the
environment could also point to a substantial portion of the
environment itself). Yet, new values and their differences may be
treated independently and, as a result, discrepancies (i.e., bugs)
may arise between new values and differences.
[0224] In summary, a language of "differences" may be incomplete
because the language of differences used to express how a first
expression differs from a second expression (i.e., either an
indexed child is different, the expression was entirely replaced or
for lists a number of removed/inserted elements at respective
indices). Moreover, difference expressions may not enable the
cloning of an element to another place, let alone any calculation
of differences after cloning. For example, when manipulating
tree-like structures (e.g., HTML), a user may observe that elements
"move" between the tree, but such movement may not be reflected in
the language of differences which, at most, described insertions
and deletions within a list. The language of differences, alone,
may not support alternatives, and as a result, any ambiguity in how
the difference exists between a first and second value may
necessitate re-running an entire update procedure.
Recursive (Edit Action) Language
[0225] In view of the foregoing, another style of evaluator may be
needed in some scenarios. Thus, in a preferred embodiment, the
present techniques include converting a first evaluator based on
recursivity to a second evaluator based on rewriting, and then
converting the second evaluator to a third evaluator producing a
description of rewriting through Edit Actions. Specifically,
instead of using differences, which have a symmetrical connotation,
an embodiment includes a powerful recursive notion including one or
more edit actions. The recursive embodiment described herein
enables authors to obtain a much more flexible bidirectional
evaluator. In fact, in some embodiments, the present techniques are
readily and successfully applied to the JavaScript and PHP
languages, to derive bidirectional evaluators much faster than
expected.
[0226] The present techniques include an inductive set of
self-contained "edit actions." Here, "self-contained" means that,
inter alia, the edit actions 1) may fully replace the tuple
(back-propagated value, differences with original), and 2) may
encode more edit actions over various scenarios (e.g., the present
techniques prove that the set of edit actions may form a monoid,
meaning that the composition between them can also be expressed as
another "edit action"). For example, instead of replaying an edit
action script, an edit action may be factored and even be handled
independently in a final user interface.
[0227] In some embodiments, edit actions may 1) encode an
evaluation step of a Program evaluated by a small-step evaluator
(Rewrite edit actions) and 2) express changes requested to the user
to the final output (Output edit actions). The present techniques
include a migration algorithm to "migrate" an Output edit action
back through a Rewrite edit action to produce a Program edit
action. The present techniques may also describe generalizing the
migration algorithm in the case of many alternative Output edit
actions (e.g., a version-space algebra). The present techniques may
include a general methodology to convert an evaluator to a "Rewrite
edit action"-producing evaluator, that computes an edit action
representing how a final value is computed from an original program
abstract syntax tree. Further, the present techniques describe how
to apply this migration algorithm to effectively create
unevaluators for multiple lambda calculus variants and computing
languages (e.g., JavaScript, PHP, etc.). The present techniques
include a comparison of the performance in view of a baseline
unevaluator and prove a speed up of orders of magnitude.
[0228] Finally, the present techniques include a lens that can
customize back-propagation behavior, and enable versions of
List.map, Tree.map that can fully take into account clones,
wrapping and unwrapping. Specifically, from one edit action, the
present techniques may extract a shape-changing edit action and a
value-changing action. For List.map and Tree.map, the
shape-changing edit action can first be applied to the input value
unmodified (e.g., clones in outputs result in clones in inputs the
same way). The resulting input value then has the same shape as the
output, and the original evaluation update algorithm can be
applied, and will convert output value-changing edit actions (e.g.,
the edit actions for the values at the leaves of the tree) back to
input value-changing edit actions and to the mapping function. By
recombining the shape-change edit action with the input
value-change edit action, the present techniques may obtain the
final edit action on the original input. Without the lens including
back-propagation behavior, the function List.map may be difficult
to implement, due to insertions and deletions. With the
above-described approach, implementing List.map is advantageously
simplified, and clones between elements of the list and trees are
supported.
A Basic Edit Action Language
[0229] In an embodiment, an edit action language is created by
first defining a set of objects the edit action language will
operate on. In some embodiments, the set of objects may be limited
to certain records (e.g., to immutable records), wherein such
records include a map from keys to certain records. For the sake of
clarity and convenience, a conventional syntax may be used to
describe such records (e.g., JavaScript for records and TypeScript
for types). However, it should be appreciated that depending on
implementation, any suitable syntax(es) may be used. An example
object may be as follows:
[0230] Object=[key: String]: Object
[0231] Examples of records are as follows:
TABLE-US-00006 { } {prog: { }} {head: {a: { } {head: {b: { }},
tail: { }}} {0: {a: { }}, 1: {b: { }}} {exp: {var: {m: { }}}, env:
{head: {name: {m: { }}, value: {d: { }}}}}
[0232] A record may be deconstructed with bracket syntax, meaning
that if k is one of the keys of the record o, then o[k] is the
value associated to k in o. To express an edit action on such
objects, there is first a notion of "reuse", some parts will be
reused as-this (with possible edit actions for some fields), some
will be cloned from somewhere else (again with possible edit
actions on some fields), some will be created from scratch (with a
possible reuse of some fields). In an embodiment, a first naive
encoding is minimalistic, in that any action is assumed to consist
either of fully cloning a sub-record (without touching it) or
creating a new record, leaving the possibility to express edit
actions for one or more children:
TABLE-US-00007 type EditAction = Clone Path | New {[key: String]:
EditAction} type Path = Cons String Path | Nil
[0233] In some embodiments, a shorthand [key1, . . . key2] is used
to mean `Cons key1 (Cons . . . (Cons key2 Nil) . . . )`. Applying
an EditAction to an object (if applicable) may create a new object
according to the following semantics:
TABLE-US-00008 apply (New {keyl: action1, key2: action2}) object =
{key1: apply action1 object, key2: apply action2 object} apply
(Clone (Cons key path)) object = apply (Clone path) object[key]
apply (Clone Nil) object = object
[0234] Further, the present techniques specify additional semantics
for transformations that include seamlessly encoding insertions and
deletions on a list, in addition to clones of elements:
TABLE-US-00009 apply (New { }) {anything: { }} = { } apply (New {a:
Clone [ }) {b: { }} = {a: {b: { }}} apply (Clone [''tail'']) {head:
{a: { }} tail: {head: {b: { }}, tail: { }}} = {head: {b: { }},
tail: { }} apply (New {head: New {a: New{ }}, tail: Clone [ ]})
{head: {b: { }}, tail: { }} = {head: {a: { }}, tail: {head: {b: {
}}, tail: { }}} apply (New {head: Clone [''head''], tail: Clone [
]}) {head: {b: { }}, tail: { }} = {head: {b: { }}, tail: {head: {b:
{ }}, tail: { }}}
[0235] A composition of differences includes a property/assertion
such that for each edit1 edit2 and object where the two members can
be defined, apply (compose edit1 edit2) object===apply edit1 (apply
edit2 object)
[0236] A function may be defined that obeys the above assertion:
[0237] compose (Clone Nil) editAction=editAction [0238] compose
editAction (Clone Nil)=editAction [0239] compose (Clone (Cons h t))
(New subActions)=compose t subActions[h] [0240] compose (New
subActions) editAction=compose (New {k: compose sub editAction for
(k: sub) in subActions}) [0241] compose (Clone (Cons head2 tail2))
(Clone (Cons head1 tail1))=let (Clone tt)=compose (Clone (Cons
head2 tail2)) (Clone tail1) in Clone (Cons head1 tt)
[0242] The above-described language may describe all possible
transformations. However, a user may encounter difficulty in
distinguishing between nodes that are entirely replaced from
scratch (i.e., new nodes), as opposed to nodes of which only a few
sub-fields were modified (i.e., update nodes).
An Advanced Edit Action Language
[0243] A preferred embodiment may include a re-defined inductive
set of edit actions, such that the fields of the cloned elements
may be modified:
TABLE-US-00010 type EditAction = Reuse(RelPath) {[key: String]:
EditAction} | New ({[key: String]: EditAction} | Integer | String |
Boolean) type RelPath ={up: Int, down: Path}
[0244] For the above edit action definition, instead of absolute
paths, relative paths are stored. Thus, at any node, an edit action
of Reuse({up: 0, down: Nil}) { } is an identity. Furthermore, the
re-defined inductive set simplifies reasoning about EditActions,
especially when such EditActions are transformed. For example, an
edit action that does not refer a parameter up an object tree may
be copied from one place to another. It should be appreciated that
similar apply and compose functions can be derived.
Example Back Propagation Implementation
[0245] The EditAction implementation described above can be used to
enable back-propagation. For example, assume an interpreter that
rewrites objects like {a: n} to values like {b: n, c: n}.
[0246] (1) Original Input: {a: 1}
[0247] the interpreter would produce the following
[0248] (2) Original Output: {b: 1, c: 1}
[0249] Let us suppose that the user comes in and modifies the
original output by modifying the value of b, and wrapping the value
of c but leaving it untouched:
[0250] (2') Modified Output: {b: 2, c: {d: 1}}
[0251] The present techniques may back-propagate these
modifications to the original program {a: 1} by combining the two
edit actions into one, which would result in:
[0252] (1') Expected modified Input: {a: {d: 2}}
[0253] The present techniques may also derive such modified input
mechanically. using the edit actions described above. The
horizontal edit action corresponding to the small-step evaluation
from (1) to (2) is:
[0254] (1) to (2): New {b: Reuse(["a"]){ }, c: Reuse(["a"]){ }}
[0255] The vertical edit action corresponding to the user
modifications from (2) to (3) is:
[0256] (2) to (2'): Reuse([ ]) {b: New(2), c: New({d: Reuse([ ]) {
}})}
[0257] Running the algorithm outputToInputEditAction((1) to (2),
(2) to (2')) yields the following:
[0258] Reuse([ ]){a: New({d: DDNew(2)})}
[0259] which, if applied to (1), would produce the expected
(1').
[0260] FIG. 8 depicts an example output to input algorithm for
enabling the back-propagation operations discussed above. The
algorithm assumes a straightforward implementation of Reuse and
New. In the algorithm, on the output, at the current location
pointed by dStackPath (the workplace), the existing element is
replaced by a clone of a tree element present elsewhere in the
output (the source). The workplace's stack path is dStackPath. By
following the output's stack paths in the hEditAction, the
algorithm recovers the paths as they come from the input. All
children diffs are recovered globally as if they were done on the
source's path., yielding a list of global differences on the
original input. If these differences consists of updates whose path
contains the prefix dSourcePathOriginal, the algorithm assumes that
they happen on the workplace in the input and were cloned from the
source in the input.
[0261] The sourceStackPath corresponds to the dStackPath+relPath.
The dPathOriginal refers to the path where the workplace came from
in the input. The dSourcePathOriginal corresponds to the path where
the source came from in the input. The clonePath refers to the
relative path between an input's workplace and an input's source.
When the edit action type is not a Reuse type, the algorithm
collects absolute differences outside of the original path. The
algorithm of FIG. 8 may store paths in a relative way, or as
absolute paths from the root of each object.
Converting an Evaluator to an EditAction-Producing Evaluator
[0262] In some embodiments, to convert an evaluator to
edit-action-producing evaluator, so that the evaluator can use
outputToInputEditAction to back-propagate edit actions on the
output to edit actions on the program, the following steps may be
used, wherein the steps are illustrated in an environment-based
call-by-value lambda calculus. The evaluate1 function below takes a
ProgState and returns a Val:
TABLE-US-00011 Exp = { type: ''var'', name: String } | { type:
''lambda'', argName: String, body: Exp} | { type: ''app'', fun:
Exp, arg: Exp} Val = { type: ''closure'', argName: String, body:
Exp, env: Env} Env = { type: ''cons'', head: {name: String, val:
Val}, tail: Env } | { type: ''nil''} ProgState ={ exp: Exp, env:
Env } evaluate(ps: ProgState): Val { if(ps.exp.type == ''lambda'')
return {type: ''closure'', argName: ps.exp.argName, body:
ps.exp.body, env: ps.env}; if(ps.exp.type == ''var'') { let env =
ps.env; while(env.head.name != ps.exp.name) env = env.tail; return
env.head.val; } let {argName, body, env } = evaluate({exp:
ps.exp.fun, env: ps.Env}); let arg = evaluate({exp: ps.exp.arg,
env: ps.Env}); return evaluate({exp: body, env: {type: ''cons'',
head: {name: argName, val: arg}, tail: env}}); }
[0263] The present techniques may make the evaluator
tail-recursive, by eliminating the need for recursion by storing
continuations as callbacks. To do so, for an evaluator that takes
programs ProgState and produces values Val, the evaluator is
refactored to take a ComputationState to return a ComputationState
and repeatedly call itself until it reaches a final value. The
ComputationState may be defined as:
TABLE-US-00012 type Computation = { type: ''Compute'', ps:
ProgState} | { type: ''Return'', value: Val} type Continuations ={
type: ''cons'', tail: Continuations, head: ComputationState =>
ComputationState} | { type: ''nil''} type ComputationState
={computation: Computation, continuations: Continuations}
[0264] The refactoring steps may include [0265] 1. Immediately
after the start of the evaluator function, if the computation is a
Return of a value, there should be a continuation left. The present
techniques call the first continuation on the computation state by
removing the first continuation, and returning the resulting
computation state. [0266] 2. After treating the Return case, the
computation has to be a Compute of a ProgState. The present
techniques reuse the code of the evaluator, with the following
changes: [0267] (a) Replace each return X; statements that do not
involve the evaluator by [0268] return {computation: {type:
"Return", value: X}, [0269] continuations: (previous
continuations)}; [0270] (b) Replace any let X=evaluate (P); C by
[0271] return {computation: P, continuations: {type: "cons", [0272]
head: ({computation: {value: X}, continuations}=> [0273] {C},
tail: (previous continuations)}} [0274] 3. Invoke the resulting
evaluator in a while-loop so that the computation can continue
until there is nothing else to compute. In the above example, the
function evaluate1_1 is obtained, which is called from the function
evaluate1:
TABLE-US-00013 [0274] evaluate1(ps: ProgState): Val { let cs
={computation: {type: ''Compute'', ps: ps}, continuations: {type:
''nil'' }}; while(cs.computation.type !== ''Return'' | |
cs.continuations.type == ''cons'') { cs = evaluate1_1(cs); } return
cs.computation.value; } evaluate1_1(cs: ComputationState):
ComputationState { if(cs.computation.type == ''Return'') {
if(cs.continuations.type == ''cons'') return
cs.continuations.head({computation: cs.computation, continuations:
cs.continuations.tail}); else throw ''Error: no way to continue
computation''; } let ps =cs.computation.ps; if(ps.exp.type ==
''lambda'') return {computation: {type: ''Return'', value: {type:
''closure'', argName: ps.exp.argName, body: ps.exp.body, env:
ps.env}, continuations: cs.continuations }}; if(ps.exp.type ==
''var'') { let env = ps.env; while(env.head.name != ps.exp.name)
env = env.tail; return {computation: {type: ''Return'', value:
env.head.val}, continuations: cs.continuations }; } return {
computation: { type: ''Compute'', ps: {exp: ps.exp.fun, env:
ps.Env}}, continuations: { type: ''cons'', tail: cs.continuations,
head: ({computation: {value: {argName, body, env} return
{computation: {type: ''Compute'', ps: {exp: ps.exp.arg, env:
ps.Env}}, continuations: {type: ''cons'', tail: continuations,
head: ({computation: {value: arg}, continuations}) => { return
{computation: {type: ''Compute'', ps: {exp: body, env: {type:
''cons'', head: {name: argName, val: arg}, tail: env}}},
continuations: continuations}; } } } } } }; }
[0275] In some embodiments, the present techniques may transform
the ComputationState to a first-order data structure. Initially,
continuations may be stored as closures, which may make them
difficult to reason about, as the above-described Edit Actions
cannot be applied directly to them. Thus, in some embodiments,
continuations may be replaced by the data they require, including
an identifier specifying which code may be called. For example, at
the beginning of the equivalent of evaluate1_1 function, instead of
calling the first continuation on the ComputationState, the present
techniques may use a case disjunction to execute code that the
original closure would have executed. In the above example, this
would yield the function evaluate1_2 that replaces the function
evaluate1_1:
TABLE-US-00014 evaluatel_2(cs: ComputationState): ComputationState
{ if(cs.computation.type == ''Return'') { if(cs.continuations.type
== ''cons'') { let {head,tail}=cs.continuations; if(head.name ==
''afterFun'') { let {computation: {value: {argName, body,
env}}}=cs; let ps =head.data.ps; return {computation: {type:
''Compute'', ps: ps}, continuations: {type: ''cons'', tail: tail,
head: {name: ''afterArg'', data: {argName, body, env}}}}; }else
if(head.name == ''afterArg'') { let {argName, body, env}=head.data;
let {computation: {value: arg}}=cs; return {computation: {type:
''Compute'', ps: {exp: body, env: {type: ''cons'', head: {name:
argName, val: arg}, tail: env}}}, continuations: tail}; } throw
''Unknown continuation'' + head.name; } else throw ''Error: no way
to continue computation''; } let ps = cs.computation.ps;
if(ps.exp.type == ''lambda'') return { computation: {type:
''Return'', value: {type: ''closure'', argName: ps.exp.argName,
body: ps.exp.body, env: ps.env} }, continuations:
cs.continuations}; if(ps.exp.type == ''var'') { let env = ps.env;
while(env.head.name != ps.exp.name) env = env.tail; return {
computation: {type: ''Return'', value: env.head.val},
continuations: cs.continuations }; } return {computation: {type:
''Compute'', ps: {exp: ps.exp.fun, env: ps.Env}}, continuations:
{type: ''cons'', tail: cs.continuations, head: {name: ''afterFun'',
data: {type: ''Compute'', ps: {exp: ps.exp.arg, env: ps.env}}}}}; }
}
[0276] In some embodiments, the evaluator may be modified to return
Edit Actions rather than ComputationState. Specifically, once the
evaluate1_2 is updated to take and return a first-order structures
consisting of only records and strings, the present techniques may
provide that evaluate1_2 rewrite the computation state. Thus,
instead of returning computations, the present techniques may
return Edit Actions that lead to the resulting computations. This
yields a new variant evaluate1_3 that calls this function
evaluate1_3_1 by applying the resulting Edit actions to the current
computation state, to obtain the next computation state. In the
above example, this technique results in the function evaluate1_3
which replaces evaluate1_2:
TABLE-US-00015 evaluate1_3(cs: ComputationState): ComputationState
{ let editAction = evaluate1_3_1(ComputationState); return
applyEditAction(editAction, cs); } evaluate1_3_1(cs:
ComputationState): EditAction { if(cs.computation.type ==
''Return'') { if(cs.continuations.type == ''cons'') { let
{head,tail} = cs.continuations; if(head.name == ''afterFun'') {
return New({ computation: New({ type: New(''Compute''), ps:
Reuse([''continuations'', ''head'', ''data'', ''ps'']), }),
continuations: New({ type: New(''cons''), head: New({ name:
New(''afterArg''), data: Reuse([''computation'', ''value'']) }),
tail: Reuse([''continuations'', ''tail'']) }) }); } else
if(head.name == ''afterArg'') { let {argName, body, env} =
head.data; let {computation: {value: arg}} = cs; return New({
computation: New({ type: New(''Compute''), ps: New({ exp:
Reuse([''continuations'', ''head'', ''data'', ''body'']) env: New({
type: New(''cons''), head: New({ name: Reuse([''continuations'',
''head'', ''data'', ''argName'']), val: Reuse([''computation'',
''value'']) }), tail: Reuse([''continuations'', ''head'', ''data'',
''env'']) }) }) }); continuations: Reuse([''continuations'',
''tail'']) }); } throw ''Unknown continuation'' + head.name; } else
throw ''Error: no way to continue computation''; } let ps =
cs.computation.ps; if(ps.exp.type == ''lambda'') { return Reuse([
],{ computation: Newatype: New(''Return''), value: New({ type:
New(''closure''), argName: Reuse([''ps'', ''exp'', ''argName'']),
body: Reuse([''ps'', ''exp'', body'']), env: Reuse([''ps'',
''env''])})})}); } if(ps.exp.type == ''var'') { let env = ps.env, n
= 0; while(env.head.name != ps.exp.name) {env = env.tail; n++; }
return Reuse([ ],{ computation: Newatype: New(''Return''), value:
Reuse([''ps'', ''env'', ...fillArray(''tail'', n), ''head''])})});
} return New({ computation: Reuse([''computation''],{ ps: Reuse([
],{ exp: Reuse([''fun'']) }) }), continuations: New({ type:
New(''cons''), head: New({ name: New(''afterFun''), data:
New({type: ''Compute'', ps: New({exp: Reuse([''computation'',
''ps'', ''exp'', ''arg''])}), env: Reuse([''computation'', ''ps'',
''env''])})})}) }), tail: Reuse([ ]) }) }); } }
[0277] Thus, Edit Actions are computed depending on the computation
state. When looking up a variable in the environment, evaluate1_3_1
also generates a custom Reuse path with the right amount of "tail"
so that the path points to the value being used. The top-level use
of Reuse ([ ], for both variables and lambdas enables the present
techniques to not have to specify that the stack of continuations
is the same, and to start specifying the reuse relative paths with
"ps" rather than absolute paths with "computation", "ps". It should
be appreciated that a Reuse could be used for the last return
statement, but in such embodiments, a relative path may be required
that goes "up" on the leaves, which may decrease readability.
Moreover, those of skill in the art will appreciate that many ways
to write the above Edit-Action-producing evaluator are envisioned.
For example, instead of building the structure {type: "Compute",
ps: . . . } in the continuation of the last return statement, an
embodiment may embed the entire computation state, and/or the
argument and the env. Some designs might be easier to reason about,
although at the end, the algorithms may produce the same
result.
Using Edit-Action-Producing Evaluators to Create Update Engines
[0278] Once the above techniques are implemented, to arrive at an
evaluator that produces Edit-Action as a byproduct, and using the
function outputToInputEditAction, as described in FIG. 8, the
present techniques may implement a procedure to update programs
when values are modified. This procedure may execute the program
only once and record intermediate Edit Actions. It should be
appreciated that recording intermediate Edit Actions is a step that
may be pre-computed before running the update function in some
embodiments. For clarity, the following description includes this
evaluation as part of the update.
[0279] In the following example, update takes a program, an edit
action that has been made on its output, and returns the new
program. For that, update applies to the old program the edit
action obtained by calling the subroutine update_1:
TABLE-US-00016 update(exp: Exp, editActionOnOutput; EditAction):
Exp { let finalEditAction = update_1(exp, editActionOnOutput);
return applyEditAction(finalEditAction, exp); } update_1(exp: Exp,
editActionOnOutput: EditAction): EditAction { let intermediates = [
]; let cs = {computation: {type: ''Compute'', ps: {exp: exp, env:
{type: ''nil''}}}, continuation while(cs.computation.type !==
''Return'' | | cs.continuations.type == ''cons'') { let ea =
evaluate1_3(cs); cs = applyEditAction(ea, cs);
intermediates.push(ea); } let finalEditAction = editActionOnOutput;
while(intermediates.length > 0) { finalEditAction =
outputToInputEditAction(intermediates.pop( ), finalEditAction); }
return finalEditAction; } indicates data missing or illegible when
filed
[0280] It should be appreciated that the while loop enables the
support of local lenses, providing users ways to define reverse
transformations themselves. The foregoing approach may be applied
to easily scale to existing interpreters, without having to
consider the update part. For example, and without limitation,
reverse interpreters may be authored for the following languages:
[0281] call by name substitution-based lambda calculus [0282] call
by value substitution-based lambda calculus [0283] call by name
environment-based lambda calculus [0284] call by value
environment-based lambda calculus [0285] Krivine evaluator [0286]
JavaScript [0287] PHP
Implementing a Krivine Evaluator
[0288] As described above, bidirectional evaluation is a technique
that allows arbitrary expressions in a standard .lamda.-calculus to
be "run in reverse". In some embodiments of bidirectional
evaluation, (1) an expression e is evaluated to a value v, (2) the
user makes "small" changes to the value yielding v' (structurally
equivalent to v), and (3) the new value v' is "pushed back" through
the expression, generating repairs as necessary to ensure that the
new expression e' (structurally equivalent to e) evaluates to
v'.
[0289] Shown below is the syntax of a pure .lamda.-calculus
extended with constants C. The present techniques may employ
natural (e.g., big-step, environment-style) semantics, where
function values are closures. Call-by-value function closures (E,
.lamda.xe) may refer to call-by-value environments E--which bind
call-by-value values--and call-by-name function closures (D,
.lamda.xe) to call-by-name environments D--which bind expression
closures (D, e) yet to be evaluated. A stack S may be a list of
call-by-name expression closures. [0290] Expressions
e::=c|.lamda.xe|x|e.sub.1 e.sub.2 [0291] Call-By-Value Values v::=c
(E, .lamda.xe) [0292] Call-By-Value Environments E::=-E, xv [0293]
Call-By-Name Values u::=c|(D, .lamda.xe) [0294] Call-By-Name
Environments D::=-|D, x(D.sub.x, e) [0295] Krivine Argument Stacks
S::=[ ] (D, e)::S
[0296] Herein, structural equivalence is defined as structural
equivalence of expressions (e.sub.1.about.e.sub.2), values
(v.sub.1.about.v.sub.2 and u.sub.1.about.u.sub.2), environments
(E.sub.1.about.E.sub.2 and D.sub.1.about.D.sub.2), and expression
closures (E.sub.1.perp.e.sub.1.about.E.sub.2.perp.e.sub.2 and
D.sub.1.perp.e.sub.1.about.D.sub.2.perp.e.sub.2) is equality modulo
constants c.sub.1 and c.sub.2, which may differ, at the leaves of
terms.
Bidirectional Call-by-Value Evaluation
[0297] FIG. 9 depicts the bidirectional call-by-value evaluation
rules, that may be extend the core language with numbers, strings,
tuples, lists, etc. In addition to a conventional "forward"
evaluator, there is a "backward" evaluator (also referred to as
"evaluation update" or simply "update"), whose behavior is
customizable. The environment-style semantics simplifies the
presentation of backward evaluation; a substitution-based
presentation would require tracking provenance.
[0298] Given an expression closure (E, e) (a "program") that
evaluates to v, together with an updated value v', evaluation
update traverses the evaluation derivation and rewrites the program
to (E', c'') such that it evaluates to v'. The first three update
rules are as follows: Given a new constant c', the BV-U-Const rule
retains the original environment and replaces the original
constant. Given a new function closure (E', .lamda.xe'), the
BV-U-Fun rule replaces both the environment and expression. Given a
new value v', the BV-U-Var rule replaces the environment binding
corresponding to the variable x used; E[xv'] denotes
structure-preserving replacement.
[0299] The rule BV-U-App for function application is what enables
values to be pushed back through all expression forms. The first
two premises evaluate the function and argument expressions using
forward evaluation, and the third premise pushes the new value v'
back through the function body under the appropriate environment.
Two key aspects of the remainder of the rule are as follows: first,
that update generates three new terms to grapple with: E'.sub.f,
v'.sub.2, and e'.sub.f. The first and third are "pasted together"
to form the new closure (E'.sub.f, .lamda.xe'.sub.f) that some new
function expression e'.sub.1 must evaluate to, and the second is
the value that some new argument expression e'.sub.2 must evaluate
to; these obligations are handled recursively by update (the fourth
and fifth premises). The second key is that two new environments
E.sub.1 and E.sub.2 are generated; these are reconciled by the
following merge operator, which requires that all uses of a
variable be updated consistently in the output. It will be
appreciated by those of skill in the art that in practice, it is
often useful to allow the user to specify a single example of a
change, to be propagated to other variable uses automatically. An
alternative merge operator may be used, that trades soundness for
practicality.
[0300] Herein, the cbv Environment Merge
E.sub.1.sup.e.sup.1.sym..sup.e.sup.2E.sub.2 is defined as
follows:
( E 1 , x v 1 ) e 1 .sym. e 2 ( E 2 , x v 2 ) = ( E ' - e 1 .sym. e
2 -= - , x v ) ##EQU00002## where E ' = E 1 e 1 .sym. e 2 E 2
##EQU00002.2## and v = { v 1 if v 1 = v 2 v 1 if x free Vars ( e 2
) v 2 if x free Vars ( e 1 ) ##EQU00002.3##
[0301] Further, two theorems apply. First, the Structure
Preservation of BV Update:
[0302] If E.perp.e.sub.BV V and v.about.v' and E.perp.e .sub.BV
v'E'.perp.e', then E.perp.e.about.E'.perp.e'.
[0303] And secondly, the Soundness of BV Update
[0304] If E.perp.E .sub.BV v'E'.perp.e', then E'.perp.e'.sub.BV
v'.
[0305] A call-by-name system largely follows the semantics of the
call-by-value version described above.
Bidirectional Call-by-Name Evaluation
[0306] FIG. 10 depicts an example of bidirectional call-by-name
evaluation. The BN-U-Const and BV-U-Fun rules are analogous to the
call-by-value versions. The BN-U-Var rule for variables must now
evaluate the expression closure to a value, and recursively update
that evaluation derivation. Being call-by-name, rather than
call-by-need, the present techniques do so every time the variable
is used, without any memoization. The BN-U-App for application is a
bit simpler than BV-U-App, because the argument expression is not
forced to evaluate; thus, there is no updated argument expression
to push back. Environment merge for call-by-name environments is
similar to the merging described above.
[0307] Several theorems apply: First, Structure Preservation of BN
Update. If D.perp.e.sub.BN u and u.about.u' and D.perp.e .sub.BN
u'D'.perp.e', then D.perp.e.about.D'.perp.e'. Second, Soundness of
BN Update. If D.perp.e .sub.BN u'D'.perp.e', then D'.perp.e'.sub.BN
u'.
[0308] In addition to being sound with respect to forward
call-by-name evaluation, it is sound with respect to forward
call-by-value evaluation. To formalize this proposition below, the
present techniques refer to the lifting .left brkt-top.E.right
brkt-bot. and .left brkt-top.v.right brkt-bot. of by-value
environments and values, respectively, to by-name versions, and to
the evaluation (D, e) of a delayed expression closure to a by-value
value.
[0309] Herein, BV Value and BV Environment Lifting are defined as
follows:
.left brkt-top.(E.sub.f,.lamda.ye).right brkt-bot.=(.left
brkt-top.E.sub.f.right brkt-bot., .lamda.ye) .left brkt-top.c.right
brkt-bot.=c
.left brkt-top.=.right brkt-bot.=-.left brkt-top.E,xc.right
brkt-bot.=.left brkt-top.E.right brkt-bot.,xH(E,c).left
brkt-top.E,x(E.sub.f,.lamda.ye).right brkt-bot.=.left
brkt-top.E.right brkt-bot.,x.left
brkt-top.(E.sub.f,.lamda.ye).right brkt-bot.
BN Value, BN Environment, and BN Closure Evaluation are defined
as:
[ c ] = c [ ( D , .lamda. x . e ) ] = ( [ D ] , .lamda. x . e ) [ -
] = - [ D , x ( D x , e ) ] = [ D ] , x [ ( D x , e ) ]
##EQU00003## [ D ] e B V v [ ( D , e ) ] = v ##EQU00003.2##
[0310] A further theorem is Completeness of BN Evaluation. If
E.perp.e.sub.BV v, then .left brkt-top.E.right
brkt-bot..perp.e.sub.BN .left brkt-top.v.right brkt-bot.. And still
further, the Soundness of BN Update for BV Evaluation. If
E.perp.e.sub.BV v and .left brkt-top.E.right brkt-bot..perp.e
.sub.BN .left brkt-top.v'.right brkt-bot.D'.perp.e', then [[D',
e']]=v'.
Krivine Evaluation
[0311] Lastly, the present techniques include a bidirectional
"Krivine evaluator" in the style of the classic (forward) Krivine
machine, an abstract machine that implements call-by-name semantics
for the lambda-calculus. While lower-level than the "direct"
call-by-name formulation, above, the forward and backward Krivine
evaluators are even more closely aligned than the prior
versions.
[0312] FIG. 11 depicts Krivine evaluator semantics, according to an
embodiment. Following the approach of the Krivine machine, the
forward evaluator maintains a stack S of arguments (i.e. expression
closures). When evaluating an application e.sub.1 e.sub.2, rather
than evaluating the e.sub.1 to a function closure, the argument
expression e.sub.2--along with the current environment D--is pushed
onto the stack S of function arguments (the K-E-App rule); only
when a function expression "meets" a (non-empty) stack of arguments
is the function body evaluated (the K-E-Fun-App rule). The
K-E-Const, K-E-Fun, and K-E-Var rules are similar to the
call-by-name system, now taking stacks into account.
[0313] The backward evaluator closely mirrors the forward
direction. Recall the two keys for updating applications (BV-U-App
and BN-U-App): pasting together new function closures to be pushed
back to the function expression, and merging updated environments.
Because the forward evaluation rule K-E-App does not syntactically
manipulate a function closure, the update rule K-U-App does not
construct a new closure to be pushed back. Indeed, only the
environment merging aspect from the previous treatments is needed
in K-U-App. The K-U-Fun-App rule for the new Krivine evaluation
form--following the structure of the K-E-Fun-App rule--creates a
new function closure and argument which will be reconciled by
environment merge. It should be appreciated that existing
approaches for turning the natural semantics formulation of the
present techniques into an abstract state-transition machine
(including the use of, e.g., markers or continuations) ought to
work for turning the natural semantics into one of the "next 700
Krivine machines".
[0314] Several theorems apply, such as Structure Preservation of
Krivine Update: If (D.perp.e; S)u and u.about.u' and (D.perp.e;
S)u'(D'.perp.e'; S'), then D.perp.e.about.D'.perp.e'. Another
theorem is Soundness of Krivine Update: If (D.perp.e; S)u and
(D.perp.e; S)u'(D'.perp.e'; S'), then (D'.perp.e'; S')u'.
[0315] The following theorem connects the Krivine system to the
above (natural-semantics style) call-by-name system (and, hence,
the above call-by-value system)--analogous to the connection
between the Krivine machine and traditional substitution-based
call-by-name systems: Equivalence of Krivine Evaluation and BN
Evaluation (-.perp.e; [ ])u iff -.perp.e.sub.BN u.
[0316] A corollary is the Soundess of Krivine Update for BN
Evaluation: If -.perp.e.sub.BN u and (-.perp.e; [ ])u'(-.perp.e'; [
]), then -.perp.e'.sub.BN u'. And, Soundess of Krivine Update for
BV Evaluation: If -.perp.e.sub.BV v and (-.perp.e; [ ]).left
brkt-top.v'.right brkt-bot.(-.perp.e'; [ ]), then -.perp.e'.sub.BV
v'.
[0317] Conclusions regarding the backward Krivine evaluator that
will be appreciated of those of skill in the art include that
first, the evaluator never creates new values (function closures,
in particular) to be pushed back (like BV-U-App and BN-U-App do).
Therefore, if the user interface is configured to disallow function
values from being updated (that is, if the original program
produces a first-order value c), then the K-U-Fun rule for bare
function expressions can be omitted from the system. And second,
unlike the call-by-value and call-by-name versions above, the
backward Krivine evaluator does not refer to forward evaluation at
all. The backward rules are straightforward analogs to the forward
rules, using environment merge to reconcile duplicated
environments. Advantageously, this simplicity helps when scaling
the design and implementation of bidirectional evaluation to
larger, more full-featured languages.
[0318] Rather than defining forward Krivine evaluation ? directly
and then establishing its connection to the call-by-name system,
some embodiments of the present techniques may define the semantics
of forward Krivine evaluation by translation to call-by-name
evaluation. For example, Krivine Forward BN Evaluation may be
defined as follows:
x D ( D , x ( D 1 , e 1 ) e x ; S ) u ( D e ; ( D 1 , e 1 ) : : S )
u D e BN u ( D e ; [ ] ) u ##EQU00004##
[0319] As with the direct by-name evaluator, the backward Krivine
evaluator may update by-name evaluation, wherein translation
differs in the empty stack case. For example, in some embodiments,
Krivine Forward BV Evaluation may be defined as follows
x D ( D , x ( D 1 , e 1 ) e x ; S ) v ( D e ; ( D 1 , e 1 ) : : S )
v [ D ] e B V v ( D e ; [ ] ) v ##EQU00005## [0320] A corollary
follows a proposition Soundness of Krivine Update for BV
Evaluation: If (E.perp.e; S)v and (E.perp.e; S).left
brkt-top.v'.right brkt-bot.(D'.perp.e'; S'), then (D'.perp.e';
S')v'. [0321] Namely, If -.perp.e.sub.BV v and (-.perp.e; [ ]).left
brkt-top.v'.left brkt-top.(-.perp.e'; [ ]), then
-.perp.e'.sub.BVv'.
Example Bidirectional Evaluation Language Implementation
[0322] As noted above, the present techniques can be applied for
many languages, such as a lambda calculus call-by-name
substitution-based language, a lambda calculus call-by-value
substitution-based language, a lambda calculus call-by-name
environment-based language, a lambda calculus call-by-value
environment-based language, and a krivine evaluation system. The
following is a proof in JavaScript, demonstrating that the results
described herein are reproducible.
JavaScript Verification Tests
[0323] The following tests demonstrate that the above
implementation behaves correctly for a series of inputs.
Standard Function Tests
TABLE-US-00017 [0324] var ntests = 0; passedtests = 0; function
shouldEqual(a, b, msg) { ntests++; let as = uneval(a), bs =
uneval(b); if(as !== bs) { console.log(msg | | "Test failed");
inspect (a) console.log("not equal to expected") inspect (b); }
else passedtests++; } debugMagicFunction = false;
shouldEqual(outputDiffsToInputDiffs( HClone("b"), DDNew(3)),
DDUpdate({b: DDNew(3)}), "Clone new");
shouldEqual(outputDiffsToInputDiffs( HUpdatead: HClone(1, "c")},
"b"), DDUpdate({d: DDNew(3)})), DDUpdate({b: DDUpdatea({c:
DDNew(3)})})) shouldEqual(outputDiffsToInputDiffs( HUpdate({d:
HClone(1, "c"), e: HClone(1, "c")}), DDUpdate({d: DDNew(3), e:
DDNew(5)})), DDUpdate({c: DDNew(3).concat(DDNew(5))}));
shouldEqual(outputDiffsToInputDiffs( HUpdate({a: HClone(1, "b")}),
DDClone("a")), DDClone("b")) shouldEqual(outputDiffsToInputDiffs(
HNew({ }, {a: HClone("b"), b: HClone("b")}), DDClone("a")),
DDClone("b")); shouldEqual(outputDiffsToInputDiffs( HUpdate({a:
HClone(1, "b")}), DDClone("b")), DDClone("b"));
shouldEqual(outputDiffsToInputDiffs( HUpdate({a: HClone(1, "b"), b:
HClone(1, "a")}), DDClone("b")), DDClone("a"));
shouldEqual(outputDiffsToInputDiffs( HUpdate({a: HClone(1, "b")}),
DDUpdate({a: DDNew(3)})), DDUpdate({b: DDNew(3)}));
shouldEqual(outputDiffsToInputDiffs( HNew({ }, {d: HClone("a"), c:
HClone("a"), b: HClone("b")}), DDUpdate({d: DDNew(3), c:
DDNew(5)})), DDUpdate({a: DDNew(3).concat(DDNew(5))}));
shouldEqual(outputDiffsToInputDiffs( HNew({ }, {a: HSame, b:
HSame}), DDUpdate({a: DDNew(2)})), DDNew(2));
shouldEqual(outputDiffsToInputDiffs( HUpdate({ b: HClone(2, "c") },
["a"]), DDUpdate({ b: DDNew(3) })), DDUpdate({ c: DDNew(3) }));
shouldEqual(outputDiffsToInputDiffs( HUpdate({ body: HUpdate({ app:
HUpdate({ arg: HClone(5, "arg") }), arg: HClone(4, "arg") }) },
{up: 0, down: cons("app", cons("body"))}), DDUpdate({body:
DDUpdate({app: DDClone("app")}).concat(DDClone("app"))})),
DDUpdate({ app: DDUpdate({ body: DDUpdate({ body: DDUpdate({app:
DDClone("app")}) }) }) }).concat( DDUpdate({ app: DDUpdate({ body:
DDUpdate({ body: DDClone("app") }) }) }) ) );
shouldEqual(outputDiffToInputDiff( HNew({ }, {a: HNew({ }, {b:
HCloneUpdate({up: 0, down: cons("c")}, {d: HClone(2, "f")}) })}),
DUpdate({a: DUpdate({b: DUpdate({d: DCloneUpdate({up: 1, down:
cons("e")}, {p: DClone(2, "d")}), e: DClone(1, "d")})})})),
DUpdate( {f: DCloneUpdate({up: 1, down: cons("c", cons("e"))}, {p:
DClone(3, "f")}) c: DUpdate({e: DClone(2, "f")}) }));
shouldEqual(outputDiffToInputDiff( HUpdate({a: HNew({ }, {k:
HClone(1, "b", "c"), p: HClone(1, "b", "d")}), b: HClone(1, "a",
"m")}), DUpdate({b: DNew({ }, {u: DSame, o: DClone(1, "a", "k"), t:
DClone(1, "a", "p")})})), DUpdate({a: DUpdate({m: DNew({ }, {u:
DSame, o: DClone(2, "b", "c"), t: DClone(2, "b", "d")})})}));
shouldEqual(outputDiffsToInputDiffs( HNew({ }, {a: HNew({ }, {b:
HCloneUpdate({up: 0, down: cons("c")}, {d: HClone(2, "f")}) })}),
DDUpdate({a: DDUpdate({b: DDUpdate({d: DDCloneUpdate({up: 1, down:
cons("e")}, {p: DDClone(2, "d")}), e: DDClone(1, "d")})})})),
DDUpdate( {f: DDCloneUpdate({up: 1, down: cons("c", cons("e"))},
{p: DDClone(3, "f")}), "f")}) c: DDUpdate({e: DDClone(2, }));
shouldEqual(outputDiffsToInputDiffs( HUpdate({a: HNew({ }, {k:
HClone(1, "b", "c"), p: HClone(1, "b", "d")}), b: HClone(1, "a",
"m")}), DDUpdate({b: DDNew({ }, {u: DDSame, o: DDClone(1, "a",
"k"), t: DDClone(1, "a", "p")})})), DDUpdate({a: DDUpdate({m:
DDNew({ }, {u: DDSame, o: DDClone(2, "b", "c"), t: DDClone(2, "b",
"d")})})})); shouldEqual(outputDiffsToInputDiffs(
HCloneUpdate(["app", "body"], {arg: HClone(3, "arg")}),
DDUpdate({arg: DDClone(1, "app")})), DDUpdate({arg: DDClone(1,
"app", "body", "app")}) ) shouldEqual(outputDiffsToInputDiffs(
HCloneUpdate(["app", "body"], {arg: HClone(3, "arg")}), DDNew({ },
{app: DDClone("app"), arg: DDClone("app")})), DDUpdate({app:
DDUpdate({body: DDNew({ }, {app: DDClone("app"), arg:
DDClone("app")})})}) ) shouldEqual(outputDiffsToInputDiffs( HNew({
}, {b: HClone(0, "a"), c: HClone(0, "a")}), DDUpdate({b: DDNew(2),
c: DDNew({ }, {d: DDSame})})), DDUpdate({a: DDNew({ }, {d:
DDNew(2)})}) ) console.log(passedtests + "/" + ntests + " tests
succeeded") process.exit( ) function doTest(testObject) { for(let
testcase of testObject.testcases) { prog1 = testcase.prog1 step1 =
testObject.hEvaluate(prog1); console.log("\n\n-------\nFor
program") inspect(prog1); console.log("Transformed through
(computed step)") inspect(step1); prog2 = applyHDiffs(step1,
prog1); inspect(prog2); //let reverseStep1 = reverseHDiffs(prog1,
step1); for(let prog2Diff of testcase.prog2diffs) {
console.log("\nIf change") inspect(prog2) console.log("by")
inspect(prog2Diff); console.log("to")
inspect(applyDDiffs(prog2Diff, prog2)); console.log("then change")
inspect(prog1); let prog1Diff = outputDiffsToInputDiffs(step1,
prog2Diff); console.log("by") inspect(prog1Diff); console.log("to")
inspect(applyDDiffs(prog1Diff, prog1)); } } }
Lazy Substitution-Based Lambda Calculus Tests
TABLE-US-00018 [0325] cbn_lambda_calculus = { / / Prog = string / /
| {lambda: name, body: Prog} / / | {app: Prog, arg: Prog}
testcases: [ {prog1: {app: {lambda: ''x'', body: {lambda: ''y'',
body: {app: {app: ''y'', arg: ''x''}, arg: ''x'' } }}, arg: ''z''},
prog2diffs: [ / / Remove first argument, or remove second argument.
DDUpdate({body: DDUpdate({app:
DDClone(''app'')}).concat(DDClone(''app''))}), / / Replace second
argument by w DDUpdate({body: DDUpdate({arg: DDNew(''w'')})}), / /
Replace First argument by w DDUpdate({body: DDUpdate({app:
DDUpdate({arg: DDNew(''w'')})})}), / / Copy function to overwrite
second argument DDUpdate({body: DDUpdate({arg: DDClone(1, ''app'',
''app'')})}), / / Add a third argument / / Clone the second
argument to a third argument z (should be x / / at the end) or add
an unrelated third argument z (should stay z / / at the end)
DDUpdate({body: DDNew({ }, {app: DDSame, arg:
DDClone(''arg'').concat(DDNew(''z''))})}) ] } ], hEvaluate(prog) {
/ / Returns the hdiff to perform. if(typeof prog.lambda !==
''undefined'') return HSame; if(typeof prog === ''string'') return
HSame; / / The function is not yet a lambda if(typeof
prog.app.lambda === ''undefined'') return HUpdate({app:
hEvaluate(prog.app)}); let name = prog.app.lambda; / / Replacement.
let evalHDiff = function(upDepth, bodyExp, cloneFrom) { / / Clone
of argument if(bodyExp === name) return HClone(upDepth, ''arg''); /
/ Other name if(typeof bodyExp === ''string'') return
HClone(cloneFrom); / /shadowing: don't touch if(bodyExp.lambda ===
name) return HClone(cloneFrom); / / Lambda with different name.
if(typeof bodyExp.lambda !== ''undefined'') return HUpdate({body:
evalHDiff(upDepth+1, bodyExp.body)}, cloneFrom); / /if(typeof
bodyExp.app !== ''undefined'') { return HUpdate({ app:
evalHDiff(upDepth + 1, bodyExp.app), arg: evalHDiff(upDepth + 1,
bodyExp.arg) }, cloneFrom); } return evalHDiff(2, prog.app.body,
cons(''app'', cons(''body''))); } }
doTest(cbn_lambda_calculus);
Substitution-Based Lambda Calculus, Computing Argument First
Tests
TABLE-US-00019 [0326] cbv_lambda_calculus = { / / Prog = string / /
| {lambda: name, body: Prog} / / | {app: Prog, arg: Prog}
testcases: [ {prog1: {app: {lambda: ''x'', body: {lambda: ''y'',
body: {app: {app: ''y'', arg: ''x''}, arg: ''x'' } }}, arg: ''z''},
prog2diffs: [ / / Remove first argument, or remove second argument.
DDUpdate({body: DDUpdate({app:
DDClone(''app'')}).concat(DDClone(''app''))}), / / Replace second
argument by w DDUpdate({body: DDUpdate({arg: DDNew(''w'')})}), / /
Replace First argument by w DDUpdate({body: DDUpdate({app:
DDUpdate({arg: DDNew(''w'')})})}), / / Copy function to overwrite
second argument DDUpdate({body: DDUpdate({arg: DDClone(1, ''app'',
''app'')})}), / / Add a third argument / / Clone the second
argument to a third argument z (should be x / / at the end) or add
an unrelated third argument z (should stay z / / at the end)
DDUpdate({body: DDNew({ }, {app: DDSame, arg:
DDClone(''arg'').concat(DDNew(''z''))})}) ] } ], hEvaluate(prog) {
/ / Returns the hdiff to perform. if(typeof prog.lambda !==
''undefined'') return HSame; if(typeof prog === ''string'') return
HSame; / / The function is not yet a lambda if(typeof
prog.app.lambda === ''undefined'') return HUpdate({app:
hEvaluate(prog.app)}); / / The argument is not yet a lambda
if(typeof prog.arg.lambda === ''undefined'') return HUpdate({arg:
hEvaluate(prog.arg)}); let name = prog.app.lambda; / / The argument
is also a lambda now if(typeof prog.arg.lambda != ''undefined'') {
let evalHDiff = function(upDepth, bodyExp, cloneFrom) { / / Clone
of argument if(bodyExp === name) return HClone(upDepth, ''arg''); /
/ Other variable if(typeof bodyExp === ''string'') return
HClone(cloneFrom) / / Shadowing: don't touch if(bodyExp.lambda ===
name) return HClone(cloneFrom); if(typeof bodyExp.lambda !==
''undefined'') return HUpdate({ body: evalHDiff(upDepth + 1,
bodyExp.body) }, cloneFrom); / /if(typeof bodyExp.app !==
''undefined'') { return HUpdate({ app: evalHDiff(upDepth + 1,
bodyExp.app), arg: evalHDiff(upDepth + 1, bodyExp.arg) },
cloneFrom); } return evalHDiff(2, prog.app.body, cons(''app'',
cons(''body''))); } } }
Environment-Based Call-By-Name Lambda Calculus Tests
TABLE-US-00020 [0327] env_cbn_lambda_calculus = { / / ProgState =
{prog: Compute | Return, continuations: List { }} / / Compute =
{ctor: ''Compute'', data: {env: Env, exp: Exp}} / / Exp = string /
/ | {lambda: string, body: Exp} / / | {app: Exp, arg: Exp} / / Env
= {name: String, value: {env: Env, exp: Exp}, tail: Env} | { } / /
Return = {ctor: ''Return'', data: {env: Env, exp: {lambda: string,
/ / body: Exp}}} continuations: { / / First element of
continuations is an applyArg applyArg: function(progState) { let
val = progState.prog.data; let cont = progState.continuations.hd;
return {prog: {ctor: ''Compute'', env: {name: val.exp.lambda,
value: cont.data, tail: val.env}, exp: val.exp.body},
continuations: progState.continuations.tl} } }, // Ground truth
evaluate(progState) { let prog = progState.prog; if(prog.ctor ===
''Return'') { if(typeof progState.continuations !== ''undefined'')
{ return continuations[progState.continuations.hd.ctor](progState);
} else { return prog; / / Final value. } } if(typeof prog.data.exp
=== ''string'') { let env = prog.data.env; while(env &&
env.name !== exp) env = env.tail; if(!env) { console.log(env);
throw exp + '' not found''; } // Replace data with env's data
return evaluate({prog: {ctor: ''Compute'', data: env.value},
continuations: progState.continuations}); } if(typeof
prog.data.exp.lambda === ''string'') { / / Compute => Return
return evaluate({prog: {ctor: ''Return'', data: prog.data},
continuations: progState.continuations}); } if(typeof
prog.data.exp.app !== ''undefined'') { // Replace exp with exp.app
// Add a continuations return evaluate({prog: {ctor: ''Compute'',
data: {env: env, exp: prog.data.exp.app}}, continuations:
cons({kind: ''applyArg'', data: {env: env, exp: prog.exp.arg}},
progState.continuations)}); } }, hContinuations: { / / First
element of continuations is an applyArg. It used to be a / / Return
applyArg(progState) { return HUpdate({ prog: HUpdate({ ctor:
HNew(''Compute''), data: HUpdate({ env: HNew({ }, { name: HClone(1,
''exp'', ''lambda''), value: HClone(3, ''continuations'', ''hd'',
''data''), tail: HSame}), exp: HClone(''body'')})}), continuations:
HClone(''tl'') }) } }, hEvaluate(progState) { let prog =
progState.prog; if(prog.ctor === ''Return'') return typeof
progState.continuations !== ''undefined'' ?
hContinuations[progState.continuations.hd.ctor](progState) : HSame;
/ / Final value. if(typeof prog.data.exp === ''string'') { let env
= prog.data.env; let downStack = cons(''value''); while(env
&& env.name !== exp) { env = env.tail; downStack =
cons(''tail'', downStack) } if(!env) { console.log(env); throw exp
+ '' not found''; } return HUpdate({prog: HUpdate({data:
HClone(cons(''env'', downStack))})}); } if(typeof
prog.data.exp.lambda === ''string'') { return HUpdate({prog:
HUpdate({ctor: HNew(''Return'')})}); } / / if(typeof
prog.data.exp.app !== ''undefined'') { / / Replace exp with exp.app
/ / Add a continuations return HUpdate({ prog: HUpdate({ data:
HUpdate({ exp: HClone(''app'')})}), continuations: HNew({ }, { hd:
HNew({kind: ''applyArg''}, { data: HClone(1, ''prog'', ''data'')}),
tl: HSame })}) ; } }
Environment-Based Call-By-Value Lambda Calculus Tests
TABLE-US-00021 [0328] env_cbv_lambda_calculus = { / / ProgState =
{prog: Compute | Return, continuations: List { }} / / Compute =
{ctor: ''Compute'', data: {env: Env, exp: Exp}} / / Exp = string /
/ | {lambda: string, body: Exp} / / | {app: Exp, arg: Exp} / / Env
= {name: String, value: {env: Env, exp: Exp}, tail: Env} |
undefined / / Return = {ctor: ''Return'', data: {env: Env, exp:
{lambda: string, body: Exp}}} continuations: { / / First element of
continuations is an computeArg, program is a Return.
computeArg(progState) { let computedFun = progState.prog.data; / /
The continuation is how to compute the argument let argCompute =
progState.continuations.hd; return { prog: {ctor: ''Compute'',
data: argCompute}, continuations: {hd: { ctor: ''applyFun'', / /
Store the env/exp lambda for later. data: computedFun }, tl:
progState.continuations.tl}} }, / / First element of continuations
is an applyFun, program is a / / Return (the argument).
applyFun(progState) { let computedFun =
progState.continuations.hd.data; let argVal = progState.prog.data;
return { prog: {ctor: ''Compute'', data: { exp:
computedFun.exp.body, env: {name: computedFun.exp.lambda, value:
argVal, tail: computedFun.env} } }, continuations:
progState.continuations.tl} } }, evaluate: function
evaluate(progState) { let prog = progState.prog; if(prog.ctor ===
''Return'') { if(typeof progState.continuations !== ''undefined'')
{ return continuations[progState.continuations.hd.ctor](progState);
} else { return prog; / / Final value. } } if(typeof prog.data.exp
=== ''string'') { let env = prog.data.env; while(env &&
env.name !== exp) env = env.tail; if(!env) { console.log(env);
throw exp + '' not found''; } / / For CBV, data is a
value=>''Return'' instead of ''Compute'' return evaluate({prog:
{ctor: ''Return'', data: env.value}, continuations:
progState.continuations}); } if(typeof prog.data.exp.lambda ===
''string'') { / / Compute => Return return evaluate({prog:
{ctor: ''Return'', data: prog.data}, continuations:
progState.continuationsI); } if(typeof prog.data.exp.app !==
''undefined'') { / / Replace exp with exp.app / / Add a
continuations return evaluate({prog: {ctor: ''Compute'', data:
{env: env, exp: prog.data.exp.app}}, continuations: cons({kind:
''computeArg'', data: {env: env, exp: prog.data.exp.arg}},
progState.continuations)}); } }, hContinuations: { / / First
element of continuations is a computeArg, program is a / / Return.
computeArg(progState) { return HUpdate({ prog: HUpdate({ ctor:
HNew(''Compute''), data: HClone(2, ''continuations'', ''hd'')}),
continuations: HNew({ }, { hd: HNewactor: ''applyFun''}, { data:
HClone(1, ''prog'', ''data'')}), tl: HClone(''tl'')})}); }, / /
First element of continuations is an applyFun, program is a / /
Return (the argument). applyFun(progState) { return HUpdate({ prog:
HUpdate({ ctor: HNew(''Compute''), data: HNew({ }, { exp: HClone(2,
''continuations'', ''hd'', ''data'', ''exp'', ''body''), env:
HNew({ }, { name: HClone(2, ''continuations'', ''hd'', ''data'',
''exp'', ''lambda''), value: HSame})})/), continuations:
HClone(''tl'')}) } }, hEvaluate(progState) { let prog =
progState.prog; if(prog.ctor === ''Return'') { if(typeof
progState.continuations ! == ''undefined'') { return
hContinuations[progState.continuations.hd.ctor](progState); } else
{ return HSame; / / Final value, no change possible. } } if(typeof
prog.data.exp === ''string'') { let env = prog.data.env; let
downpath = cons(''value''); while(env && env.name !== exp)
{ env = env.tail; downpath = cons(''tail'', downpath) } if(!env) {
console.log(env); throw exp + '' not found''; } / / For CBV, data
is a value=>''Return'' instead of ''Compute'' return HUpdate({
prog: HUpdate({ ctor: HNew(''Return''), data: HClone(cons(''env'',
downpath))})}); } if(typeof prog.data.exp.lambda === ''string'') {
/ / Compute => Return return HUpdate({prog: HUpdate({ctor:
HNew(''Return'')})}); } if(typeof prog.data.exp.app !==
''undefined'') { / / Replace exp with exp.app / / Add a
continuations return HUpdate({ prog: HUpdate({data: HUpdate({exp:
HClone(''app'')})}), continuations: HNew({ }, { hd: HNew({kind:
''computeArg'', data: HCloneUpdate({up: 1, down: cons(''prog'',
cons(''data''))}, { exp: HClone(''arg'')})}), tl: HSams})}); } }
}
Krivine Evaluator Tests
TABLE-US-00022 [0329] / / In the krivine evaluator, there is no
continuation, the remaining / / computations for arguments are
inside the argStack krivine_evaluator = { / / ProgState = Compute |
Return / / Compute = {ctor: ''Compute'', closedExp: {env: Env, exp:
Exp}, / / argStack: ArgStack} / / Return = {ctor: ''Return'', data:
{env: Env, exp: / / {lambda: string, body: Exp}}} / / Exp = string
/ / | {lambda: string, body: Exp} / / | {app: Exp, arg: Exp} / /
ClosedExp = {env: Env, exp: Exp} / / ArgStack = {head: ClosedExp,
tail: ArgStack} | undefined / / Env = {name: String, value:
ClosedExp, tail: Env} | undefined evaluate(prog) { if(prog.ctor ===
''Return'') { throw ''Cannot call a program on an error''; } let
closedExp = prog.closedExp; if(typeof closedExp.exp === ''string'')
{ let {env, exp} = closedExp; while(env && env.name !==
exp) env = env.tail; if(!env) { console.log(env); throw exp + ''
not found''; } return evaluate( {ctor: ''Compute'', closedExp:
env.value, argStack: prog.argStack}); } if(typeof
prog.closedExp.exp.lambda === ''string'') { if(typeof typeof
prog.argStack != ''undefined'') { return evaluate( {ctor:
''Compute'', closedExp: {env: {name: closedExp.exp.lambda, value:
prog.argStack.head, tail: closedExp.env}, exp: closedExp.exp.body},
argStack: prog.argStack.tail}); } else { / / Only return place
return evaluate({ctor: ''Return'', data: prog.closedExp}); } }
if(typeof prog.exp.app !== ''undefined'') { / / Replace exp with
exp.app / / Add a continuations return evaluate( {ctor:
''Compute'', closedExp: {env: env, exp: closedExp.exp.app},
argStack: {head: {env: env, exp: closedExp.exp.arg}, tail:
prog.argStack}}); } }, hEvaluate(prog) { if(prog.ctor ===
''Return'') { throw ''Cannot call a program on an error''; } let
closedExp = prog.closedExp; if(typeof closedExp.exp === ''string'')
{ let {env, exp} = closedExp; let downpath = cons(''value'');
while(env && env.name !== exp) { downpath = cons(''tail'',
downpath); env = env.tail; } if(!env) { console.log(env); throw exp
+ '' not found''; } return HUpdate({closedExp: HClone(cons(''env'',
downpath))}); } if(typeof prog.closedExp.exp.lambda === ''string'')
{ if(typeof typeof prog.argStack != ''undefined'') { return HNew({
}, {ctor: HNew(''compute''), closedExp: HNew({ }, { env: HNew({ },
{ name: HClone(''closedExp'', ''exp'', ''lambda''), value:
HClone(''argStack'', ''head''), tail: HClone(''closedExp'',
''env'')}), exp: HClone(''closedExp'', ''exp'', ''body'')}),
argStack: HClone(''argStack'', ''tail'')}); } else { return HNew({
}, {ctor: HNew(''Return''), data: HClone(''closedExp'')}); / / Only
return place } } if(typeof prog.exp.app !== ''undefined'') { / /
Replace exp with exp.app / / Add a continuations return HNew({ },
{closedExp: HCloneUpdate(''closedExp'', {exp: HClone(''app'')}),
argStack: HNew({ }, {head: HCloneUpdate(''closedExp'', {exp:
HClone(''arg'')}), tail: HClone (''argStack'' ) }) }) ; } }, }
[0330] The above JavaScript implementation and tests are for
illustration purposes only and should not be considered limiting of
other approaches. For example, those of skill in the art will
appreciate that while the above tests are applicable to the above
JavaScript implementation, additional implementations/application
programming interfaces (e.g., PHP) are envisioned, with additional
respective test suites. In fact, the above techniques are
applicable to all programming languages now known or later
developed.
Additional Considerations
[0331] It should be appreciated that the tedium of the
edit-run-view cycle described above may not be a mere annoyance.
Computer programmers and other technology workers often spend hours
per day typing on their keyboards, and over time, the repeated
stress of such typing can cause injury. Insofar as the present
techniques reduce the need for users to use physical input devices
(e.g., mice, keyboards, etc.), additional, less intuitive/expected
benefits may be realized, such as increased productivity leading to
lower labor costs. The productivity gains discussed above with
respect to software development also represent significant
advancements in the state of the art, and are significant
improvements to computer functionality. Specifically, text editors
do not currently support bidirectional evaluation as described
herein.
[0332] The following considerations also apply to the foregoing
discussion. Throughout this specification, plural instances may
implement operations or structures described as a single instance.
Although individual operations of one or more methods are
illustrated and described as separate operations, one or more of
the individual operations may be performed concurrently, and
nothing requires that the operations be performed in the order
illustrated. These and other variations, modifications, additions,
and improvements fall within the scope of the subject matter
herein.
[0333] Unless specifically stated otherwise, discussions herein
using words such as "processing," "computing," "calculating,"
"determining," "presenting," "displaying," or the like may refer to
actions or processes of a machine (e.g., a computer) that
manipulates or transforms data represented as physical (e.g.,
electronic, magnetic, or optical) quantities within one or more
memories (e.g., volatile memory, non-volatile memory, or a
combination thereof), registers, or other machine components that
receive, store, transmit, or display information.
[0334] As used herein any reference to "one embodiment" or "an
embodiment" means that a particular element, feature, structure, or
characteristic described in connection with the embodiment is
included in at least one embodiment. The appearances of the phrase
"in one embodiment" in various places in the specification are not
necessarily all referring to the same embodiment.
[0335] As used herein, the terms "comprises," "comprising,"
"includes," "including," "has," "having" or any other variation
thereof, are intended to cover a non-exclusive inclusion. For
example, a process, method, article, or apparatus that comprises a
list of elements is not necessarily limited to only those elements
but may include other elements not expressly listed or inherent to
such process, method, article, or apparatus. Further, unless
expressly stated to the contrary, "or" refers to an inclusive or
and not to an exclusive or. For example, a condition A or B is
satisfied by any one of the following: A is true (or present) and B
is false (or not present), A is false (or not present) and B is
true (or present), and both A and B are true (or present).
[0336] In addition, use of "a" or "an" is employed to describe
elements and components of the embodiments herein. This is done
merely for convenience and to give a general sense of the
invention. This description should be read to include one or at
least one and the singular also includes the plural unless it is
obvious that it is meant otherwise.
[0337] Upon reading this disclosure, those of skill in the art will
appreciate still additional alternative structural and functional
designs for implementing the concepts disclosed herein, through the
principles disclosed herein. Thus, while particular embodiments and
applications have been illustrated and described, it is to be
understood that the disclosed embodiments are not limited to the
precise construction and components disclosed herein. Various
modifications, changes and variations, which will be apparent to
those skilled in the art, may be made in the arrangement, operation
and details of the method and apparatus disclosed herein without
departing from the spirit and scope defined in the appended
claims.
* * * * *