On Point-free Programming

This is a comment I posted on LtU regarding “concatenative programming”. I repost it here because this is something I hope to write for a long time but didn’t make the time to do it.

Concatenative programming, or point-free style, has some serious problems, similar to SKI combinators. Applicative programs can be compiled into them easily, but writing them directly is a mental burden for both the programmer and the readers.

It works well only when the parties of concatenation are functions with very few argument. But concatenation of functions with more than two arguments will heavily depend on the order of currying of the arguments. If the receiver has an argument order that is very different from the sender’s output order, non-trivial permutation of argument order would happen. Notice that even mathematics uses names for permutations (as in algebra):

(a b c d)
(b a d c)

A function with four arguments has 4! = 24 different argument orders. How do you manage connecting it to inputs?

If we connect these functions with point-free style, it would be very confusing as which value is going where because the interface will be hidden in a connector specialized for the permutation!

Concatenative programming is like connecting the components of a circuit. But even electronic engineers don’t do it this way. They use net-lists with names and labels.

Names are essential and useful in most cases. Concatenative programming, although nice when used sparsingly (for example as the predicate to the filter function), should not be a major way of composition.

At this point, I found this sentence from Tao Te Ching (Chapter 1) especially relevant:

“The nameless is the origin of Heaven and Earth
The named is the mother of myriad things”


JavaScript Grammar Bug

JavaScript’s grammar as in ECMA-262 (since Version 1, 1997) has a non-trivial bug. Here is the problematic production:

LeftHandSideExpression :
  NewExpression
  CallExpression

This rule basically says “A function call is a left-value.” This will allow you write something like f(x)++ or f(x)=42, but disallow you write y=f(x).

Since this bug has been there for 15 years, I guess nobody really looked at ECMA-262 when writing parsers for JavsScript ;-)


Reason and Proof

I have always been dissatisified with mathematical proofs, no matter how rigorous they are. I can understand and verify the proofs, but they often teach me very little. My math professors have always been frustrated by me, because I ask more questions than they could answer.

I: “So you have proved this theorem, but you haven’t shown me what makes it true.”

Prof: “I just gave you a proof. Can’t you understand it?”

I: “I understand every step of the proof, but the theorem is true not because you have proved it. It has been true since before we existed. I mean, how would somebody come up with this theorem in the first place?”

Prof: “Well, I don’t know. You need to be extremely smart in order to do that…”

Now I understand that, my objection to proofs is ligitimate. There is a crucial difference between reason and proof. What I want (but often fail to get from my professors) is the underlying reason.

The reason that something is true is often deeper and simpler, but harder to find than, a proof that it is true. There can be many proofs of the same truth, but there is often only one reason that makes it true. To put it into analogy: the evidence of a crime doesn’t cause the crime to happen. The crime happens because of a much deeper reason, so does the theorem.


Towards Structural Version Control

  • Can we write programs without using any syntax?
  • How do we perform version control when structural programming becomes prevalent?
  • Why are all text-based version control systems fundamentally flawed with respect to merging?
  • How do we find the difference between the parse trees of two programs?

My talk (Feb 10, 2012) was intended to answer these questions.

The talk slides are here: [PPT] [PDF]

The PPT version is preferred because I made many animations to illustrate the ideas. I don’t know how to convert the animations into PDF. I’d appreciate if anybody tell me how to do that.


Decoupling Type Classes

(I posted this idea to Haskell Cafe some days ago and got interesting respons from Dominique Devriese, who implemented instance arguments [1,2] (Agda’s equivalence of type classes). The disucssion has been really interesting, so I decided to collect and organize the ideas into a coherent post here. Some syntax is borrowed from their work.)

This post relates the three ideas together: type classes, implicit parameters, and typed dynamic scoping. The first two are researched into some degree but their relationship and design are still subject to debates. The latter is my own analogy which appears to be really helpful clarifying those design decisions.

For the impatient, I have bulletted points with all the main ideas in this post here:

  • We can achieve the same or more expressiveness of type classes without using dictionaries. We just need implicit parameters.
  • Implicit parameters and their types can be fully inferred.
  • Implicit parameters can express MPTCs.
  • Type classes can be thought of as dynamic typing, though more disciplined.

To Group, or not to group.

Haskell’s type classes tie the two concepts “overloading” and “grouping” together, but in fact they are not necessarily related. Decoupling those two concepts has several benefits:

  • It makes the type system more flexible.
  • It makes the implementation more modular.
  • It frees us from the need to pass a whole dictionary when we only use one function in it.

I’m curious why we need to group those functions into a type class, instead of using them directly as overloaded functions. So I built an experimental type system which “decouples” the dictionary. Instead of passing a dictionary, it passes individual “implicit parameters” to the function. Each implicit parameter corresponds to an “undefined variable” in the function. Those implicit parameters are type inferenced and can contain type parameters just as the functions in a type class. Similarly, they are resolved by their types in the call site’s scope.

The convenience of this approach compared to Haskell’s type classes is that we no longer require a user of a type class to define all the methods in a type class. For example, a user could just define a method + without defining other methods in the Num class: -, *, … He can use the method + independently. For example, if + is defined on the String type to be concatenation, we can use + in another function:

weirdConcat x y = x + y + y

This has a utility, because the methods in the Num class don’t “make sense” for Strings except +, but the current type class design requires us to define them. This is burdensome. By separating the Num class into individual overloaed functions, we enables this kind of overloading without any extra work.

Then a question arises: how do we relate the types of functions if we no longer have this grouping? For example, for the Monad class, we expect return to be of type (a -> m a) if (>>=) is of type (m a -> (a -> m b) -> m b).

The answer is to use a polymorphic record to group the functions when needed, then reference the functions through the record.

Thus we have delegated the task of overloading to separate overloaded functions, and the task of grouping to records. This is a more modular design compared to the original type class system of Haskell.

Expressing MPTC

There is another benefit of this decoupling: it is expressive enough to subsume MPTC. Here is why: because the methods are no longer grouped, there is no longer a “common” type parameter to the methods. Thus we can easily have more than one parameter in the individual methods and conveniently use them as MPTC methods.

For example,

overload g
 ... ...
f x (Int y) = g x y

Notice that in my system, there are no explicit declarations containing type variables. The declaration “overload g” is all that is needed. The system infers f’s type as:

a -> Int -> {{g: a -> Int -> b}} -> b

Here it automatically infers the type for g (a -> Int -> b) just from its usage inside f, as if we have written a type class definition like:

class G a where
 g :: a -> Int -> b

So in this system, not only we don’t need to defined type classes, we don’t even need to declare the types of the overloaded functions! We can infer them from their usage and different calls to the same function don’t even need to have the same type! All it takes is:

overload g

And even this is not really necessary. It is for sanity purposes – to avoid inadvertent overloading of unbound variables.

So if g is used as:

f x y (Int z) = g x z y

then we infer its type as:

a -> b -> Int -> {{ g :: a -> Int -> b -> c}} -> c

and g will be equivalent to the one you would have defined in a MPTC method.

Some Implementation Details

Here is how it can be implemented. When we see an “undefined” variable in a function definition which has been declared as “overloaded function”, we store the function name, and the type variables that are associated with it. For example,

overload g -- (explicitly declare g as an overloaded function)

f x y (String s) = ...
...
let z = g x s y in
...
...

We don’t know what x and y are, but we know from the body of f that their types satisfy this pattern: g a String b. Thus we store this pattern constraint as an extra (implicit) argument in the type of f:

f :: a -> b -> String {{g: g a String b}}

We may have multiple such arguments.

At the call sites of f, we look for a function g in the scope that satisfies the pattern g a String b, but we don’t pass on the substitution, so they remain polymorphic. Once found, the function is passed as an extra parameter to f. This is essentially dictionary passing, but without grouping. It can be also more efficient because the parameters may be stored in registers.

Here g is explicitly declared as “overloaded”, although my experimental system doesn’t need this. Any undefined variable inside function body automatically becomes overloaded. This may cause unintended overloading and it catches bugs late. That’s why we need the “overload” declarations.

But the automatic overloading of the undefined may be useful in certain situations. For example, if we are going to use Haskell as a shell language. Every “command” must be evaluated when we type them. If we have mutually recursive definitions, the shell will report “undefined variables” either way we order the functions. The automatic overloading may solve this problem. The undefined variables will temporarily exist as automatic overloaded functions. Once we actually define a function with the same name AND satisfies the type constraints, they become implicit parameters to the function we defined before. If we call a function whose implicit parameters are not associated, the shell reports error very similar to Haskell’s “type a is not of class Num …”

Type Classes are Typed Dynamic Scoping

It appears to be helpful to think of the calls to overloaded functions as referencing dynamically scoped variables. They are dispatched depending on the bindings we have in the call site’s scope (and not the scope where the method is defined!). So it is very much similar to the much-hated dynamic scoping. But the dispatching is more disciplined — it doesn’t just match the name. It must match both the name and the inferred type. So in general it is useful and doesn’t cause trouble.

This intuition also explains why local instances shouldn’t be allowed, because if we capture the variables at the definition site, the method call becomes “statically scoped”.

Let me post an example from Oleg:

foo x =
 let overload bar (x:Int) = x + 1
 in \() -> bar x

baz =
 let overload bar (x:int) = x + 2
 in foo (1::Int)

Here at the call site foo (1::Int), bar should be resolved to the one inside baz, and not the one inside foo. My analogy to “dynamic scoping” is useful to understand the reason here. If we capture the bar inside foo’s definition site, it would correspond to “lexical scoping”.

Footnotes:

[1] https://lirias.kuleuven.be/handle/123456789/304985

[2] http://wiki.portal.chalmers.se/agda/pmwiki.php?n=ReferenceManual.InstanceArguments


Register Allocation Is Not Graph Coloring

With R. Kent Dybvig, I designed a new method for register allocation. Different from earlier methods, it departs from the graph coloring paradigm, and is based on the so-called “model transformer semantics”, which is essentially abstract interpretation. I have the feeling that register allocation is not really a graph coloring problems, but rather a cache replacement or scheduling problem. The paper is currectly under consideration of submission.

Here is the abstract of the paper:

Register allocation has long been formulated as a graph coloring problem, coloring the conflict graph with physical registers. Such a formulation does not fully capture the goal of the allocation, which is to minimize the traffic between registers and memory. Linear scan has been proposed as an alternative to graph coloring, but in essence, it can be viewed as a greedy algorithm for graph coloring: coloring the vertices not in the order of their degrees, but in the order of their occurence in the program. Thus it suffers from almost the same constraints as graph coloring. In this article, I propose a new method of register allocation based on the ideas of model transformer semantics (MTS) and static cache replacement (SCR). Model transformer semantics captures the semantics of registers and the stack. Static cache replacement relaxes the assumptions made by graph coloring and linear scan, aiming directly at reducing register-memory traffic. The method explores a much larger solution space than that of graph coloring and linear scan, thus providing more opportunities of optimization. It seamlessly performs live range splitting, an optimization found in extensions to graph coloring and linear scan. Also, it simplifies the compiler, and its semantics-based approach provides possibilities of simplifying the formal verification of compilers.

Its full text can be found [here]. I also have given a pl-wonks talk about it in Nov 2011. Here are the slides [PPT] [PDF].


ydiff: Structural Comparison of Programs

(Click on the above picture to see it in action. See the end of the post for more demos!)

I neglected to write a proper introduction to ydiff, but I think it is about time to write something about it.

Motivation

Although I call it a “semantic diff tool”, the purpose of designing ydiff is more far-reaching than it seems. I have long been imagining a world where programs are not represented as text, but as data structures (see this post). They will be editted not with text editors, but with structural editors, which create and manipulate the abstract syntax trees (AST) of programs directly. I have been talking to people about this idea, but very few thought this as an important change, until recently I found a post that points out the importance of this idea. I’m happy that another person realized that.

So let’s assume that structural editing is the future of programming. How do we compare programs and do version control in a world where programs are stored not as text streams, but as structured data? The line-by-line diff utilities and version control systems will simply stop to work, because we no longer have “lines” or “words” in our programs!

Well, ydiff is my answer to this futuristic problem. It is a diff tool (and hopefully evolving into a full-fledged version control system) designed for the upcoming “structural editing age” of programming.

Currently ydiff takes pains to first parse program text into ASTs and do structural comparison on the trees, but in the future, programs will be stored as data structures. The parsing step will be gracefully skipped, and ydiff will be readily extensible to all programming languages.

The following is the introduction to ydiff and the demos taken from my homepage

Why ydiff?

We very often want to know how our code is modified. This is normally done by using the diff program. While effective for finding differences between close revisions, the result from diff can be very hard to read for revisions over a long time span. The worst case is when the code is massively rearranged or refactored, where diff may consider every line to have been changed.

ydiff can help in this kind of situation. Different from line-based or character-based diff, ydiff is “language-based”. It parses the programs and then performs structural comparison on the parse trees. In addition to the usual functionalities of diff, it has the following properties:

  • Language-awareness. ydiff truly understands language constructs and will not make non-sensical comparisons. For example it will never compare a string “10000″ with an integer 10000 even though they look similar. Also, it tries to find definitions with the same name before it attempts to destruct and compare definitions of different names.
  • Refactor detection. It can find renamed, moved, reordered, wrapped, lifted, combined or fragmented code. Refactored code can be detected however deep they are into the structures.
  • Format insensitivity. The comparison result will not be affected by linebreaks or whitespaces.
  • Comprehensible output. The output of ydiff is designed for human understanding. The interactive UI helps the user navigate and understand changes efficiently.

These properties make ydiff helpful for understanding changes. It may also be possibly used for detecting plagiarism in programming classes or copyright infringement of code.

Supported Languages

Currently ydiff supports the following languages:

  • Scheme
  • Emacs Lisp
  • Python
  • Javascript
  • C++

The supporting part of a language is just a parser for that language, into a unified parse tree data structure. I have designed a parser combinator library to make parser-writing less painful, but they are still quite tricky to write. Hopefully in the future, the programs will be edited by structural editors and stored as data structures, so no parsers will be needed any more!

Demos

Before going further into the design details, you are invited to take a look at the demos. They are interactive HTML files with a nice UI design. The left and right windows are always locked in their relative position. A mouse click on changed, moved or unchanged nodes will highlight the matched nodes and scroll the other window to match. After that, the windows will be locked into their new relative position for browsing.

Okay, here are the demos.

  • Scheme (mk-mk-c.html)

    Comparison of the original miniKanren from Professor Dan Friendman and the version I modified in order to support condc, a “negation operator”. Pay attention to the function unify, whose major part is moved into unify-good.

  • Emacs Lisp (paredit20-paredit22.html)

    Comparison of two versions (v20 and v22) of Taylor Campbell’s paredit-mode, a structural editing mode of emacs for Lisp programs.

  • Arbitrary S-expressions (pass1-pass2.html)

    Trying to find a bug in an optimizing pass of my Scheme compiler by comparing the IRs (represented as S-expressions).

  • Python (demo1-demo2.html)

    Comparison of two implementations of a small list library that I wrote, which implements Lisp-style lists. The first implementation uses recursive procedures while the second uses Python’s generator syntax and is iterative. Pay some attention to append, whose code is moved inside another function appendAll.

  • Javascript (nav-nav-div.html)

    Comparison between two major revisions of the UI code of ydiff itself.

  • C++ demo1 (d8-3404-d8-8424.html)
    and C++ demo2 (simulator-mips-simulator-arm.html)

    There are two demos for C++. The first demo compares two versions of the d8 Javascript debugger from the V8 project (v3404 from 2009 and v8424 from 2011). The second demo compares V8′s simulators for two different processors (MIPS and ARM).

    The d8 demo is especially interesting because by clicking on the lines of the method Shell::Initialize in the old version, it can be clearly observed that its body has been distributed into several procedures in the new version:

    Shell::Initialize
    Shell::CreateGlobalTemplate
    Shell::RenewEvaluationContext
    Shell::InstallUtilityScript
    

    Also the major part of Shell::Main is moved into the helper Shell::RunMain.

Algorithms and Technical Details

There are basically two main ideas inside ydiff: Tree Editing Distance and Substructure Extraction, plus the use of the neat idea of parser combinators (found in Parsec).

  • Tree Editing Distance. Tree Editing Distance measures difference between two trees. In our case, it is the abstract syntax tree. When we encounter two trees, how do we compare them? If the first nodes of them appear to be the same, then we simply proceed to the next right? But what if the first nodes are different? There are three possibilities:
    1. The programmer inserted a node into the first tree.
    2. The programmer deleted a node from the first tree.
    3. The programmer modified a node in the first tree.

    We have to consider those possibilities for each level of nodes, so this naturally becomes a dynamic programming problem. Normally dynamic programming is done by cleverly find out the order to evaluate the problem space, but ydiff uses another important technique: memoization. There is a two dimensional hash table which stores the “delta” (changes) of two nodes, indexed by the two nodes themselves. The dynamic programming is then done in a recursive way, instead of the usual iterative way. The hash table automatically reduces the cost of recursion to the magnitude of iteration, by avoiding redundant evaluation of the nodes that we already compared.

  • Substructure Extraction. Tree Editing Distance can only find the “edits” — insertions, deletions and modifications, but it cannot find refactorization of code. If a piece of code is lifted out of a scope, the Tree Editing Distance function will produce two big changes: a deletion of a big node (for example, an if-statement), and an insertion of a slightly smaller node (with the “if (condition) { … }” wrap stripped off). Notice that although we only made a small change to the program, the resulting delta can be large. What we really want is that it shows a deletion of the wrap (“if (condition) { … }”), but a movement of the parts inside the wrap. This is called Substructure Extraction.

    Substructure Extraction works by recursively compare the substructures of two nodes, finding possible matches between one node and the subnodes of another. This substructural comparison is only done on the pairs of the resulting insertions and deletions from the Tree Editing Distance function. This little trick captures almost all possible refactorizations between two programs, however deep they are. This is demonstrated in the following picture, where ydiff successfully detects the movement of the function append into another function appendAll.

  • Parser Combinators. The parsers of ydiff are written with the help of a parser combinator library (in Scheme). In addition to the common features of Parsec, it detects left recursion and reports a “stack trace” of the left recursion, so that the parser writer can find the origin of left-recursion and fix the problem accordingly. This helps a lot when constructing the parsers. For examples, I constructed the JavaScript parser in one day, and the C++ parser in just two days. There are corner cases missing from the C++ parser and especially the macro part, but it parses significant pieces of code correctly (for example Google’s V8 project code).

Get the code

ydiff is an open source project. You can follow its development on github: yinwang0/ydiff or get its source code from there.


A Minimal Boot Sector

The natural first step of building an operating system is to find a way to run programs on “bare hardware”. The task turns out to be quite easy despite its daunting first impression. After researching on the net and trying out various different tutorials on operating systems writing, I decided that all of them are too complicated for my purpose. Most of them assume using a C-like languages as the implementing language, and thus contain code that fits into that kind of framework. Since I will not be using C, I have more freedom of choosing how my boot sector works. After learning the good parts from the tutorials and applying my own simplifications, I arrived at my first boot sector. It is very simple and does very little (It just boots the machine and displays a colorful banner.), but it illustrates my design philosophy quite well.

The criteria I had for the boot sector are:

  • Minimal. It should not do more than illustrating how to boot a computer.
  • Interesting. It should do something interesting so that I can know that the computer is indeed booted. Currently it just displays some colorful text.
  • Independent. Because my OS will be a departure from most popular software systems in existence, it should use as little “library code” as possible. Examples of “library code” include:
    • The BIOS service routines such as “int 10H”. Instead of using them to display text, I decided to write into the VGA memory directly.
    • Complex instructions such as string operations (movsb, scasb, etc.). These instructions are complicated. They clobber certain registers and are not necessarily more efficient. In other words, I’m pursuing a design similar to RISC.
    • The processor’s “push”, “pop” and “ret” instructions. These instructions forces certain language-specific calling conventions and should be avoided.
    • Segmentation and paging. These processor features forces C-style memory management. The high-level programming language I’m designing will hopefully eliminate the need for C entirely. It will take care of memory management itself at object-level. Paging hardware and TLB will be no longer needed in the system.

The code of the boot sector turns out to be very short (only 21 lines of code excluding comments and blank lines).

org 7C00H                      ; the program will be loaded at 7C00H

start:
  mov eax, string_start
  mov ch, 1                    ; ch contains color of text
  mov ebx, 0B8000H + 718H      ; B8000H is VGA memory
                               ; 718H is offset to "center"

print:
  mov cl, [eax]                ; load char into cl
  mov [ebx], cx                ; store [color:char] from cx into VGA
  add ch, 1                    ; change color to (ch+1) mod 16
  and ch, 0x0F
  add eax, 1                   ; advance string pointer
  add ebx, 2                   ; advance VGA pointer
  cmp eax, string_end          ; until the end of string
  jg stop
  jmp print

stop:
  jmp stop                     ; infinite loop after printing

  string_start db 'My colorful new OS!'
  string_end equ $

  times 510-($-$$) db 0        ; pad remainder of boot sector with 0s
  dw 0xAA55                    ; standard PC boot signature

It is in NASM syntax and needs nasm to be assembled into machine code. After that it can be booted by QEMU, a processor emulator. The only two necessary command lines are (assuming the code is stored in a file named myfirst.asm):

nasm -f bin -o myfirst.bin myfirst.asm
qemu -hda myfirst.bin

Of course, you can also burn the disk image (myfirst.bin) onto a CD and boot a real machine from it!


Sum Types vs Open Unions

I discussed this idea with many people but convinced few of them, but they found no reasonable disproof of it either. I’m just going to record it here for my own records.

In a new type system I’m designing, I was trying to find a good reason to get rid of sum types (as are commonly used in ML and Haskell). Well, I don’t hate them that much, but for simplicity’s sake, I always try to get rid of things unless there are undeniable reasons that they must exist. I think sum types have a large overlapping of functionality with product types and cause inelegance and inefficiency. So I hope to replace them with “open unions”, which are more orthogonal with respect to product types. I seem to have found a good idea where sum types originated and the reason why we don’t need them in a programming language.

It is quite evident that sum types were orginated from some branch of mathematics. In those mathematical context, there are no (named) product types. All we have are Cartesian products. Cartesian products have no constructors (or type tags) in them. How do we distinguish a student represented as (Name x Weight) with a professor, also represented as (Name x Weight)? We put tags on them before putting them into a “disjoint union”. This is called an “injection”. We do this every time when putting anything into a disjoint union, and we “project” the elements out when we need their values.

From a programming language’s point of view, the injections and projections are very inefficient and inconvenient. This is equivalent to constructing new objects whenever we need to put them into a list, and destructing them when we need their components. Then construct new objects (again!) when we need to put them into another list, and so on …

In order to avoid these shortcomings, programming language researchers decided to attach type tags to the objects when they are created. The tags stay with the objects throughout their life-time. The result is the so-called “record” or “product” types. But somehow the PL researchers seemed to have been influenced by mathematics’ way. They decided that each variant of a “union” needs to define a new constructor (injection). Thus sum types were born. For example,

data T1 = Foo Int | Bar String

Here Foo and Bar are in essence injections. If you take a closer look, there isn’t any difference between a sum type and a disjoint union. Why do we need the injections and projections in a language where type tags always tell us what an object is? Another problem is that a constructor can only belong to one sum type. There is no way we can reuse it in another sum type. For example, there is no way you can define another sum type like:

data T2 = Foo Bool | Baz Float

because Foo is already defined in T1. What I wanted to express is that Foo belongs to the union T1 and it also belongs to the union T2. This is a very reasonable request and I can imagine how often I need it, but type systems of ML and Haskell don’t allow me to do that.

If we relax the requirement that each variant of a sum type must define a new constructor, we get “open unions”. Open unions have been implemented in OCaml (in the name “polymorphic variants”) and Typed Racket and seem to be well-behaved. They are more general and orthogonal with respect to product types. We can always choose to put another level of type tags upon the variants, therefore we lose nothing if we get rid of sum types.

ACKNOWLEDGEMENTS: Thanks to Oleg Kiselyov and Ryan Newton for pointing out that OCaml has open unions since a long time ago.


Boolean Expression Simplification

A friend of mine posed an interesting question to me a few days ago. He was trying to make a search engine for an advertisement system where each advertisement belongs to several categories (or keywords). He would like to implement a search system that can efficiently find advertisements using a simple expression language containing boolean expressions. For example, he would like to have expressions like ((“football” or “basketball”) and “baby”). For efficiency, he would like that the boolean expressions be simplified before they are used to collect streams of data items.

After a while, we decided that the boolean expressions should be converted into a “sum of products” (SoP) form so as to make the search efficient. So I made a prototype of the algorithm which can turn boolean expressions (containing “and”, “or” and “not”) into SoP form.

It turns out to be quite simple if we do it recursively. The basic idea is to:

  1. Push ‘and’ into ‘or’.
  2. Push ‘not’ into ‘and’ and ‘or’
  3. Do (1) and (2) repeatedly until no more simplifications can be made.

It is easy to see that we will have a SoP form after this transformation, because the rules (1) and (2) will be applicable if there are and’s outside of or’s, or there are not’s outside of and’s and not’s. Thus we are assured to have a SoP form when this process terminates.

The rest of the problem is how to represent the final result. I proposed to group and terms so as to reduce nesting. For example, the form (and (and a b) c) is not as good as (and a b c), because the latter contains the implicit information that a, b and c are interchangeable in their order of evaluation, so we may achieve accelerations in the algorithm by searching for keyword which contains fewer items first. This simplification in the result can be achieved with a rewriting procedure which is “context-aware”. We pass the context types into the recursive calls, and the recursion gives us different results in accordance with the context types.

The algorithm for doing these simplifications is prototyped in Scheme and uploaded to my GitHub repository:

http://github.com/yinwang0/experimental-code/blob/master/boolean-simp.ss