Metaprogramming
Literals¶
Codon supports compile-time metaprogramming through the
special Literal
type. Literal
s represent constants which
can be operated on and manipulated at compile time.
There are three types of Literal
s:
Literal[int]
: integer constantLiteral[str]
: string constantLiteral[bool]
: boolean constant
Integer literals¶
Integer literals can be defined explicitly as follows:
n: Literal[int] = 42
Literals must be known at compile time. For example, the following code will cause a compilation error:
from sys import argv
n: Literal[int] = len(argv) # error: value is not literal!
Arithmetic operations on integer literals result in other integer literals:
n: Literal[int] = 42
m: Literal[int] = n + 1 # valid int literal
Conditional expressions on literals also result in other literals:
n: Literal[int] = 42
m: Literal[int] = n//2 if n%2 == 0 else 3*n + 1
Integer literals can be passed as function arguments, and also returned from functions:
def fib(n: Literal[int]) -> Literal[int]:
return 1 if n < 2 else fib(n - 1) + fib(n - 2)
n: Literal[int] = fib(10) # computed entirely at compile time!
String literals¶
Much like integer literals, string literals represent constant strings:
s: Literal[str] = 'hello'
Whereas integer literals can be manipulated via arithmetic operations to produce other integer literals, string literals can be manipulated via string operations to produce new string literals:
t: Literal[str] = s[1] # 'e'
u: Literal[str] = s[3:] # 'lo'
Much like integer literals, string literals can similarly be used in literal conditional expressions and as function argument or return types.
Boolean literals¶
Finally, boolean literals represent constant booleans:
b: Literal[bool] = True
Boolean operators can be used on boolean literals to produce new boolean literals:
b: Literal[bool] = True
d: Literal[bool] = not b # False
Some operations on integer and string literals produce boolean literals:
n: Literal[int] = 42
s: Literal[str] = 'hello'
b: Literal[bool] = (n < 10) # False
d: Literal[bool] = (s[2:4] == 'll') # True
Static loops¶
It is also possible to express loops where the loop index is a literal
integer, via the codon.static
module:
import codon.static
for i in static.range(10):
m: Literal[int] = 3*i + 1
print(m)
Static loops are unrolled at compile time, which allows the loop index to take on literal values.
Static loops can also be used to create tuples, the lengths of which must be compile-time constants in Codon:
import codon.static
t = tuple(i*i for i in static.range(5))
print(t) # (0, 1, 4, 9, 16)
You can loop over another tuple by obtaining its length as an integer
literal via static.len()
:
import codon.static
t = tuple(i*i for i in static.range(5))
u = tuple(t[i] + 1 for i in static.range(static.len(t)))
print(u) # (1, 2, 5, 10, 17)
Static evaluation¶
Literal expressions can be used as conditions in if
statements, which
enables the compiler to eliminate branches that it knows wil not be
entered at runtime. This can be used to avoid type checking errors, for
example:
def foo(x):
if isinstance(x, int):
return x + 1
elif isinstance(x, str):
return x + '!'
else:
return x
print(foo(42)) # 43
print(foo('hello')) # hello!
print(foo(3.14)) # 3.14
Normally, Codon's type checker would flag an expression like x + 1
as
an error if the type of x
is str
. However, in the code above, the
branches that are not applicable to the type of x
are eliminated so as
to allow the code to type check and compile.
Here is another, more involved example:
def flatten(x):
if isinstance(x, list):
for a in x:
flatten(a)
else:
print(x)
flatten([[1,2,3], [], [4, 5], [6]]) # 1, 2, ..., 6
Standard static typing on this program would be problematic since, if x
is an int
, it would not be iterable and hence would produce an error on
for a in x
. Static evaluation solves this problem by evaluating
isinstance(x, list)
at compile time and avoiding type checking the block
containing the loop when x
is not a list.
Static evaluation works with literal expressions, isinstance()
, hasattr()
and type comparisons like type1 is type2
.