Introduction
MetaFun is a program to compile functional programs into C++ template metaprograms. It allows you to write programs in the very Haskell-like language Kiff (for Keep It Fun & Functional), and then use them as compile-time C++ metaprograms.
You can download the latest version of MetaFun from GitHub.
Kiff: The input language
MetaFun's input language, Kiff, is a simplified version of Haskell. It supports the following features of Haskell:
- Algebraic data types (data)
- (!) Type synonyms (type)
- Declared types for definitions
- Parametric polymorphism (but not typeclasses)
- The following builtin keywords:
- let ... in ...
- (!) λ ... -> ...
- if ... then ... else ...
- Special [x, y] syntax for lists
(note that features marked with a (!) are not actually supported by the MetaFun compiler yet.)
Kiff also includes the following primitives, mapped to the native C++ operators and types:
- The types Bool and Int
- The bool literals True and False
- Numeric literals
- Bool operators &&, || and !
- Int comparison: ==, !=, <, <=, >=, >
- Int operators: +, -, *, /, %
To give you an idea of what Kiff code looks like, here's a possible definition of sum:
foldl :: (a -> b -> a) -> a -> [b] -> a foldl f x [] = x foldl f x (y:ys) = foldl f (f x y) ys add x y = x + y --
The compiler
The C++ code generated by the compiler consists of a bunch of template structs, one for each definition. Constructors are represented as structs with template parameters for constructor parameters. Bools and Ints are boxed.
For reference, here's MetaFun's C++ output for the above code of sum:
template< int > struct Int; template< bool > struct Bool; struct nil; template< class, class > struct cons; template< template< class, class > class, class, class > struct foldl; template< class, class > struct add; template< class > struct sum; template< int x > struct Int { static const int v = x; }; template< bool b > struct Bool { static const bool v = b; }; template< template< class, class > class f, class x > struct foldl< f, x, nil > { typedef x v; }; template< template< class, class > class f, class x, class y, class ys > struct foldl< f, x, cons< y, ys > > { typedef typename foldl< f, typename f< x, y >::v, ys >::v v; }; template< class x, class y > struct add { typedef Int< (x::v) + (y::v) > v; }; template< class xs > struct sum { typedef typename foldl< add, Int< 0 >, xs >::v v; };
Caveats
MetaFun is the result of me spending a week in bed because of a knee injury, and toying around with the concept of an easy-to-use language to write compile-time programs. The fact that it was thrown together in a couple of days shows: MetaFun is but a proof-of-concept demo. It's barely finished enough to compile the 9-digit solution program below. Here's a list of some notable known problems:
- No currying in function calls
- No support for lambda expressions
- The type checker doesn't actually support type synonyms
- Zero optimization: Local definitions are lifted even when they could be local nested structs in C++
A detailed example
The following program solves the 9-digit problem using a simple backtracking algorithm. In fact, it is the result of a direct, manual backwards translation of Károly Lőrentey's solution, and was the motivating example behind writing MetaFun.
length [] = 0 length (first:rest) = 1 + (length rest) value :: [Int] -> Int value [] = 0 value (first:rest) = 10 * (value rest) + first contains :: Int -> [Int] -> Bool contains elem [] = False contains elem (first:rest) = elem == first || (contains elem rest) divisor_test :: [Int] -> Bool divisor_test (first:rest) = let num = value (first:rest) div = (length rest) + 1 mod = num % div in mod == 0 test_candidate :: [Int] -> Bool test_candidate (first:rest) = divisor_test (first:rest) && !(contains first rest) search_i :: Int -> Bool -> Bool -> [Int] -> Int search_i len True True (digit:rest) = value (digit:rest) search_i len True False (digit:rest) = search len (1:digit:rest) search_i len good final (digit:rest) = search len ((digit+1):rest) search :: Int -> [Int] -> Int search len [] = search len [1] search len [10] = -1 search len (10:next:rest) = search len ((next+1):rest) search len (digit:rest) = let good = test_candidate (digit:rest) final = good && 1 + (length rest) == len in search_i good final run = search 9 []