λ2 Installation and use

Getting λ2

Our PLDI paper is available here

λ2 is available as a CDE package for Linux systems. Additionally, λ2 can be installed from source on Unix-like operating systems.

Running λ2

Simply running l2 will start our benchmark suite. Every problem that we discuss in the last section of our paper will be run by this suite. To reproduce the running times with deduction turned off, pass the -i -x flags. To reproduce the running times with type safe search turned off, pass the -u flag. Benchmark problems can be run individually with l2 NAME . For more information on the command line parameters to l2 , pass the -h flag.

l2 can also read specifications from standard input. There are a few example specifications in the specs/ directory. For example, to run the provided specification for list reverse, cat specs/reverse.l2 | ./l2 -s .

Building from source

If the compiled binary doesn't work, it is possible to build λ2 from source. The source code is included in the binary package, and instructions are below.

Linux

Install OCaml and OPAM. Install Menhir and Core with opam install core menhir . Extract the source package and run ./build.sh timing.native to build the benchmark suite.

OS X

I recommend installing OCaml and OPAM through Homebrew. Run brew install ocaml opam to install. Install Menhir and Core with opam install core menhir . Extract the source package and run ./build.sh timing.native to build the benchmark suite.

The l2 program in the root of the package is a wrapper script around the CDE packaged timing.native , so the usage instructions are the same for both.

Writing specifications

A λ2 specification consists of two parts. First, an optional set of extra primitive operators. Second, a set of input-output examples. The two sections are separated by a blank line.

Input-Output examples

To specify a function, provide a set of examples that are representative of the function's behavior. Make sure to capture any relevant special cases. For example, a specification of a function that computes absolute value should have examples on both positive and negative inputs.

λ2 supports integers, booleans, lists and n-ary trees. We have a set of predefined primitive operators over these types and a set of combinators that capture common recursion schemes.

The syntax of an input-output example is:

example ::= ( name value+ ) -> value value ::= integer | boolean | list | tree list ::= [ value* ] tree ::= { value tree* } integer ::= [0-9]+ boolean ::= #t | #f name ::= [a-zA-Z]+

The following is a specification for list reverse:

(reverse []) -> [] (reverse [1]) -> [1] (reverse [1 2]) -> [2 1] (reverse [1 2 3]) -> [3 2 1]

There are more example specifications in the specs/ directory of the package.

Providing extra primitives

Some of the problems that we discussed in the paper extend the set of primitive operators with problem specific functions. If it is known that a particular function is needed, providing it as a primitive can significantly speed up synthesis.

These extra primitives are written in the λ2 intermediate language, which is a dialect of the lambda calculus.

The syntax of the intermediate language is:

expr ::= name | ( let name expr expr ) | ( lambda ( name* ) expr ) | ( expr expr* ) | list | tree | integer | boolean list ::= [ value* ] tree ::= { value tree* } integer ::= [0-9]+ boolean ::= #t | #f name ::= [a-zA-Z]+

We support the following built-in operators:

(+) :: Int -> Int -> Int (-) :: Int -> Int -> Int (/) :: Int -> Int -> Int (*) :: Int -> Int -> Int (%) :: Int -> Int -> Int (=) :: a -> a -> Bool (!=) :: a -> a -> Bool ( Int -> Bool (>) :: Int -> Int -> Bool (<=) :: Int -> Int -> Bool (>=) :: Int -> Int -> Bool (&) :: Bool -> Bool -> Bool (|) :: Bool -> Bool -> Bool (~) :: Bool -> Bool if :: Bool -> a -> a -> a cons :: a -> [a] -> [a] car :: [a] -> a cdr :: [a] -> [a] tree :: a -> [Tree a] -> Tree a children :: Tree a -> [Tree a] value :: Tree a -> a

As well as the following combinators:

map :: [a] -> (a -> b) -> [b] filter :: [a] -> (a -> Bool) -> [a] foldr :: [a] -> (b -> a -> b) -> b -> b foldl :: [a] -> (b -> a -> b) -> b -> b mapt :: Tree a -> (a -> b) -> Tree b foldt :: Tree a -> ([b] -> a -> b) -> b -> b

Each extra primitive should be on its own line preceded by its name. The following is a specification for a program that flattens a tree into a list that uses an extra primitive:

join (lambda (a) (foldl a (lambda (c b) (foldr c (lambda (e d) (cons d e)) b)) [])) (flatten {}) -> [] (flatten {1}) -> [1] (flatten {1 {2} {3}}) -> [1 2 3]