code-conjure
synthesize Haskell functions out of partial definitions
https://github.com/rudymatela/conjure#readme
| Version on this page: | 0.6.10 |
| LTS Haskell 24.16: | 0.7.8 |
| Stackage Nightly 2025-10-25: | 0.7.8 |
| Latest on Hackage: | 0.7.8 |
code-conjure-0.6.10@sha256:3db374e24325321f9c3aa1e379d0422581ce2db7aab8d5bc38a631cd3fee60e5,4875Conjure
Conjure is a tool that synthesizes Haskell functions out of partial definitions.
Installing
To install the latest Conjure version from Hackage, just run:
$ cabal update
$ cabal install code-conjure
Prerequisites are express, leancheck and speculate. They should be automatically resolved and installed by Cabal.
NOTE: the name of the Hackage package is code-conjure
– not to be confused with Conjure the BitTorrent client.
Starting from Cabal v3.0, you need to pass --lib as an argument to
cabal install to install packages globally on the default user environment:
$ cabal install code-conjure --lib
If you already have Conjure installed Cabal may refuse to update to the latest version. To update, you need to reset your user’s cabal installation with:
rm -rf ~/.cabal/{bin,lib,logs,share,store} ~/.ghc/*/
WARNING: the above command will erase all user-local packages.
Synthesizing functions
To use Conjure, import the library with:
import Conjure
Then, declare a partial definition of a function to be synthesized. For example, here is a partial implementation of a function that computes the factorial of a number:
factorial :: Int -> Int
factorial 1 = 1
factorial 2 = 2
factorial 3 = 6
factorial 4 = 24
Next, declare a list of primitives that seem like interesting pieces in the final fully-defined implementation. For example, here is a list of primitives including addition, multiplication, subtraction and their neutral elements:
primitives :: [Prim]
primitives = [ pr (0::Int)
, pr (1::Int)
, prim "+" ((+) :: Int -> Int -> Int)
, prim "*" ((*) :: Int -> Int -> Int)
, prim "-" ((-) :: Int -> Int -> Int)
]
Finally, call the conjure function,
passing the function name, the partial definition and the list of primitives:
factorial :: Int -> Int
-- 0.1s, testing 4 combinations of argument values
-- 0.8s, pruning with 27/65 rules
-- 0.8s, 3 candidates of size 1
-- 0.9s, 3 candidates of size 2
-- 0.9s, 7 candidates of size 3
-- 0.9s, 8 candidates of size 4
-- 0.9s, 28 candidates of size 5
-- 0.9s, 35 candidates of size 6
-- 0.9s, 167 candidates of size 7
-- 0.9s, tested 95 candidates
factorial 0 = 1
factorial x = x * factorial (x - 1)
Conjure is able to synthesize the above implementation in less than a second in a regular laptop computer.
It is also possible to generate a folding implementation like the following:
factorial x = foldr (*) 1 [1..x]
by including enumFromTo and foldr in the background.
For more information, see the eg/factorial.hs example and
the Haddock documentation for the conjure and conjureWith functions.
Synthesizing functions over algebraic data types
Conjure is not limited to integers,
it works for functions over algebraic data types too.
Consider the following partial definition of take:
take' :: Int -> [a] -> [a]
take' 0 [x] = []
take' 1 [x] = [x]
take' 0 [x,y] = []
take' 1 [x,y] = [x]
take' 2 [x,y] = [x,y]
take' 3 [x,y] = [x,y]
Conjure is able to find an appropriate implementation given list constructors, zero, one and subtraction:
> conjure "take" (take' :: Int -> [A] -> [A])
> [ pr (0 :: Int)
> , pr (1 :: Int)
> , pr ([] :: [A])
> , prim ":" ((:) :: A -> [A] -> [A])
> , prim "-" ((-) :: Int -> Int -> Int)
> ]
take :: Int -> [A] -> [A]
-- 0.2s, testing 153 combinations of argument values
-- 0.2s, pruning with 4/7 rules
-- ... ... ... ... ...
-- 0.4s, 5 candidates of size 9
-- 0.4s, tested 15 candidates
take 0 xs = []
take x [] = []
take x (y:xs) = y:take (x - 1) xs
The above example also takes less than a second to run in a modern laptop. The selection of functions in the list of primitives was minimized to what was absolutely needed here. With a larger collection as primitives YMMV.
Synthesizing from specifications (for advanced users)
Conjure also supports synthesizing from a functional specification
with the functions conjureFromSpec and conjureFromSpecWith
as, in some cases,
a partial definition may not be appropriate
for one of two reasons:
- Conjure may fail to “hit” the appropriate data points;
- specifying argument-result bindings may not be easy.
Take for example a function duplicates :: Eq a => [a] -> [a]
that should return the duplicate elements in a list without repetitions.
We will first try to use a partial definition to arrive at an appropriate result.
Then we’ll see how to use conjureFromSpec.
Let’s start with the primitives:
primitives :: [Prim]
primitives = [ pr ([] :: [Int])
, prim "not" not
, prim "&&" (&&)
, prim ":" ((:) :: Int -> [Int] -> [Int])
, prim "elem" (elem :: Int -> [Int] -> Bool)
, prif (undefined :: [Int])
]
Now here’s a first attempt at a partial definition:
duplicates' :: [Int] -> [Int]
duplicates' [] = []
duplicates' [1,2,3,4,5] = []
duplicates' [1,2,2,3,4] = [2]
duplicates' [1,2,3,3,3] = [3]
duplicates' [1,2,2,3,3] = [2,3]
Here is what conjure prints:
> conjure "duplicates" duplicates primitives
duplicates :: [Int] -> [Int]
-- testing 1 combinations of argument values
-- pruning with 21/26 rules
-- ... ... ...
duplicates xs = xs
The generated function clearly does not follow our specification. But if we look at the reported number of tests, we see that only one of the argument-result bindings of our partial definition was used. Conjure failed to hit any of the argument values with five elements. (Since Conjure uses enumeration to test functions these values have to be kept “small”).
Here is a second attempt:
duplicates :: [Int] -> [Int]
duplicates [0,0] = [0]
duplicates [0,1] = []
duplicates [1,0,1] = [1]
Here is what conjure now prints:
> conjure "duplicates" duplicates primitives
duplicates :: [Int] -> [Int]
-- testing 3 combinations of argument values
-- pruning with 21/26 rules
-- ... ... ...
duplicates [] = []
duplicates (x:xs) = if elem x xs then [x] else []
The duplicates function that Conjure generated is still not correct.
Nevertheless, it does follow our partial definition. We have to refine it.
Here is a third attempt with more argument-result bindings:
duplicates :: [Int] -> [Int]
duplicates [0,0] = [0]
duplicates [0,1] = []
duplicates [1,0,1] = [1]
duplicates [0,1,0,1] = [0,1]
Here is what Conjure prints:
duplicates [] = []
duplicates (x:xs) = if elem x xs then x:duplicates xs else []
This implementation follows our partial definition, but may return duplicate duplicates, see:
duplicates [1,0,1,0,1] = [1,0,1]
Here is a fourth and final refinement:
duplicates :: [Int] -> [Int]
duplicates [0,0] = [0]
duplicates [0,1] = []
duplicates [1,0,1] = [1]
duplicates [0,1,0,1] = [0,1]
duplicates [1,0,1,0,1] = [0,1]
duplicates [0,1,2,1] = [1]
Now Conjure prints a correct implementation:
> conjure "duplicates" duplicates primitives
duplicates :: [Int] -> [Int]
-- 0.2s, testing 6 combinations of argument values
-- 0.3s, pruning with 21/26 rules
-- ... ... ... ... ...
-- 2.1s, 1723 candidates of size 17
-- 2.1s, tested 1705 candidates
duplicates [] = []
duplicates (x:xs) = if elem x xs && not (elem x (duplicates xs))
then x:duplicates xs
else duplicates xs
In this case,
specifying the function with specific argument-result bindings
is perhaps not the best approach.
It took us four refinements of the partial definition to get a result.
Specifying test properties perhaps better describes what we want.
Again, we would like duplicates to return all duplicate elements
without repetitions.
This can be encoded in a function using holds from LeanCheck:
import Test.LeanCheck (holds)
duplicatesSpec :: ([Int] -> [Int]) -> Bool
duplicatesSpec duplicates = and
[ holds 360 $ \x xs -> (count (x ==) xs > 1) == elem x (duplicates xs)
, holds 360 $ \x xs -> count (x ==) (duplicates xs) <= 1
] where count p = length . filter p
This function takes as argument a candidate implementation of duplicates
and returns whether it is valid.
The first property states that all duplicates must be listed.
The second property states that duplicates themselves must not repeat.
Now, we can use the function conjureFromSpecWith to generate the same duplicates function
passing our duplicatesSpec as argument:
> conjureFromSpecWith args{maxSize=18} "duplicates" duplicatesSpec primitives
duplicates :: [Int] -> [Int]
duplicates [] = []
duplicates (x:xs) = if elem x xs && not (elem x (duplicates xs)) then x:duplicates xs else duplicates xs
(in 1.5s)
For more information see the eg/dupos.hs example and
the Haddock documentation for the conjureFromSpec and conjureFromSpecWith functions.
The functions conjureFromSpec and conjureFromSpecWith also accept specifications
that bind specific arguments to results.
Just use == and && accordingly:
duplicatesSpec :: ([Int] -> [Int]) -> Bool
duplicatesSpec duplicates = duplicates [0,0] == [0]
&& duplicates [0,1] == []
&& duplicates [1,0,1] == [1]
&& duplicates [0,1,0,1] == [0,1]
&& duplicates [1,0,1,0,1] == [0,1]
&& duplicates [0,1,2,1] == [1]
With this, there is no way for Conjure to miss argument-result bindings.
Related work
Conjure’s dependencies. Internally, Conjure uses LeanCheck, Speculate and Express. LeanCheck does testing similarly to QuickCheck, SmallCheck or Feat. Speculate discovers equations similarly to QuickSpec. Express encodes expressions involving Dynamic types.
Program synthesis within Haskell.
MagicHaskeller (2007) is another tool
that is able to generate Haskell code automatically.
It supports recursion through
catamorphisms, paramorphisms and the fix function.
Igor II (2010) is able to synthesize Haskell
programs as well.
Hoogle (2004) is a search engine for Haskell functions. It is not able to synthesize expressions but it can find functions that match a type. Hoogle+ (2020) is similar to Hoogle but is able to search for small expressions. In addition to the type, Hoogle+ allows users to provide tests that the function should pass.
Program synthesis beyond Haskell.
PushGP (2002) and G3P (2017) are genetic programming systems that are able to synthesize programs in Push and Python respectively. Differently from Conjure or MagicHaskeller, they require around a hundred tests for traning instead of just about half a dozen.
Barliman (2016) for Lisp is another tool that does program synthesis.
Further reading
For a detailed documentation of each function, see Conjure’s Haddock documentation.
The eg folder in the source distribution
contains more than 60 examples of use.
Conjure, Copyright 2021-2025 Rudy Matela, distribued under the 3-clause BSD license.
Changes
Changelog for (Code) Conjure
v0.6.10 (February 2025)
- fix bug preventing Conjure to work in the presence of argument types whose tiers enumeration would have no values of the first tier
- allow configuring
maxPatternDepth. Default 1, interesting 2. - slightly increase default
maxSearchTests - slight improvements in performance by improve memo tables
- improve internal tests
- add
eg/tuple
v0.6.8 (February 2025)
- add
guardto provide guard as a primitive - fix bug in
maxConstantSizehandling
v0.6.6 (February 2025)
- (pruning) test candidate cases earlier and indepentently
(can be disabled with
earlyTests=False); - (pruning) rewrite after filling in recursions;
- improve showing of test values;
- some internal refactoring and code cleanup;
- update documentation of new interfaces with runtimes in examples and removal of uneeded uses of maxSize.
v0.6.4 (February 2025)
- Create
Args.targetas the main setting to configure a target number of candidates to explore. This should relate more closely with runtime.target = 10080is the default. - relax
maxSize=12tomaxSize=24by default. - show runtime in the output by default
- some internal refactoring
v0.6.2 (February 2025)
- don’t require 0 as a base case by default
(defalt to
requireZero=False) - add switch to limit the size of constant sub-expressions
(
Args.maxConstantSize) - add switch to enable showing of allowed patterns
(
Args.showPatterns) - update examples
v0.6.0 (February 2025)
Argsrecord: addshowCandidates,showTestsandshowDeconstructionsto make it easier to see what Conjure is doing.- require
0as a base case when recursing overNums, can be disabled by settingrequireZero=FalseonArgs. conjpure*: return a record rather than a tuple.- improve main documentation and introductory examples in Haddock and README.
- improve Haddock documentation a bit throughout
(
conjureIsDeconstruction,deriveConjurable, etc). - slightly improve examples and benchmarks
- (internal) improve debugging mechanisms of
Defnevaluation functions.
v0.5.16 (January 2025)
- slightly improve search space pruning
- slightly cleanup code
- fix build of tests on GHC >= 9.10
- improve tests and CI scripts
v0.5.14 (February 2024)
- improve commutative pruning, slightly faster Conjure
- add
carryOn,rewriting,requireDescent,adHocRedundancyswitches to theArgsrecord - add more benchmarks and tests
v0.5.12 (February 2024)
- bump Speculate requirement to v0.4.20
- improve testing of Conjure itself
- improvements and fixes of Conjure’s benchmarks
v0.5.10 (February 2024)
- improve pruning of candidate functions;
Conjure: un-export experimentalequalModuloTestingConjure.Conjurable: addconjureListFor,conjureSizeFor&conjureGrounds;- Reorganize internal modules
- Add
Conjure.Defn.Redundancymodule for redundant candidates; - Add
Conjure.Defn.Testmodule testing candidate definitions; - Add
Conjure.Reasonmodule for term-rewriting-based reasoning; - Add
Conjure.Redmodule for recursive descent; Conjure.Expr: add a few auxiliary functions- Move functions out of
Conjure.Engineinto new modules - add more examples and benchmarks;
- improved testing of Conjure itself;
- and several other improvements and fixes.
v0.5.8 (January 2024)
- prune redundant mutants using a few new rules
- rework numeric recursion criteria
- improve pretty-printing
- improve error handling
- refactor and lint throughout
- Conjurable tuples up to 12-tuples
- bump Express requirement to v1.0.14 (bugfix)
v0.5.6 (November 2023)
Conjuremodule: no main API changesConjure.Engine: addequalModuloTestingConjure.Utils: add some misc functions- add
bench/redundantsthat reports groups of redundant candidates
v0.5.4 (November 2023)
This has been the “dev” version after v0.5.2 for almost a couple of years:
- report invalid theories-from-testing
- weed-out some redundant candidates:
- add and use
isRedundantDefn - update how deconstructions are handled
- add and use
- add (but not use)
conjureSizetoConjurable
v0.5.2 (March 2022)
- show number of tested candidates
- complete
Conjurablederivation functions - reference related work on README
- add switch to unique-modulo-testing candidates (slow) to allow computing the near upper/lower limit on pruning
v0.5.0 (September 2021)
- allow synthesizing/conjuring from properties with
conjureFromSpec; - complete Haddock documentation;
- remove several unused functions;
- add stub
conjurableDerivefunctions; - Makefile: add targets to run GPS(2) and TerpreT benches.
v0.4.4 (September 2021)
- remove need for explicit deconstructions:
- use
-and1instead ofdec; - allow
modanddivas deconstructions;
- use
- bump Express requirement to v1.0.6 (bugfix);
- complete the GPS1 benchmark;
- add GPS2 and TerpreT benchmarks;
- minor fixes in the README.
v0.4.2 (August 2021)
- default to using top-level patterns on generated functions;
- memoize function evaluation;
- double-check theory at the end and report warning on incorrect properties;
- add
priftoConjure; - simplify deconstructor discovery and add
conjureSizetoConjurable; - add
cevaluate,cevalandcvltoConjure.Conjurable; - add
bench/gpsandbench/lowtests; - improve tests and benchmarks.
v0.4.0 (July 2021)
- background primitives are now provided with
prandprim. - report number of rules used in pruning
- require Express v1.0.4 and Speculate v0.4.12
- allow
..notation - add benchmarks, replicate, subset, p12, p30 and candidates
- add and use the
Defntype andconjureDefns - minor changes in benchmarks
- cleanup unused code
v0.3.6 (June 2021)
- add switch for descending recursions
to allow generation of
gcd - refactor recursion generation (replace a hole later)
- change
conjpureWithto takeArgs - rename two args fields to
maxBodyRecursionsandmaxEvalRecursionsat this point, the old names were misnomers.
v0.3.4 (June 2021)
- reallow recursions under
&&and||(simplifies the generatedor,and,setandelemfunctions) - only require deconstructions on a non-empty subset of arguments
(allows
fib01to be produced) - limit number of terminal evaluations in
recursiveToDynamic - fix bug in
recursiveToDynamic(not counting some recursions) - add 4 new benchmarks:
count,gcd,treeandsetelem
v0.3.2 (June 2021)
- significant runtime reduction in several benchmarks, e.g.:
- take is now reachable in about 5 seconds
- improved candidate generation:
- faster runtime
- fewer redundant/invalid candidates
- limit recursive calls to use deconstructors
- test to find deconstructors automatically
- improve recursion evaluation method (
revaluatereplacesrecursexpr) - add fibonacci benchmark
- minor:
- record runtimes with one decimal place instead of two
- add longshot benchmark
- add intercalate to the list benchmark
- add stub
Conjure.Constructorsmodule
v0.3.0 (May 2021)
- only automatically include an
iffor the return type of the given function - add the
take-dropbenchmark - make bottom-up enumeration more type directed
v0.2.8 (May 2021)
- export the
A,B,C,D,EandFhelper types
v0.2.6 (May 2021)
- require Express v0.1.10 due to
hasHolebeing now exported there - require Eq result on
conjure1,conjure2andconjure3 - code cleanup and more tests
v0.2.4 (May 2021)
- allow conjuring from specifications in addition to partial definitions
(
conjure1,conjure2,conjure3and related functions) - improve examples
- improve criteria for automatic primitive inclusion:
- only include
if :: ... -> Boolif there areBoolprimitives - include
FalseandTrueautomatically only on Speculate’s background
- only include
- add code-optional candidate nubbing and debug functions
v0.2.2 (May 2021)
- by default, search for 60 argument combinations among 100000 enumerated combinations
v0.2.0 (May 2021)
- search until 100% match is found and exit
- other misc changes
v0.1.2 (April 2021)
For the changelog of earlier versions, check the git commit history.