[ Please note that in the following discussion I will be using PM 0.5 notation, which is slightly different to PM 0.4 and also in some flux as PM 0.5 is still in development. ]
Many programming languages (and most newly developed ones) include some form of compile-time programming, ranging from simple preprocessors (#define, #if in C) to fully blown macro systems capable of re-writing the abstract syntax tree during compilation (Rust, Julia, etc.). In line with its philosophy of keeping things as simple as possible, but not simpler, PM takes a middle road with compile-time programming supported primarily through the type system. There is nothing too radical here – this is not an area where the language aims to break new ground.
The PM approach centres around the use of compile-time types. Many languages use special types to denote literal values and PM follows this trend. Literal integers, reals, strings and Booleans each have their own types: literal(int), literal(real), literal(string) and literal(bool) respectively. A value of a literal type has no associated run-time storage, it is a purely compile-time entity. A run-time value is only created when a literal type is converted to a corresponding basic type, e.g. literal(int) to int. This occurs automatically when the literal value is passed as an argument to a procedure call. Thus, in the classic:
print(“Hello World”)
the string “Hello World” is a compile-time value, which is only converted to a run-time representation when it is passed to print. So far, so pedantic. However, it is possible to define a procedure that specifically accepts a literal value, and in this case no automatic conversion occurs:
proc double(x:literal(int))=x+x
double(2)
Here double(2) returns the compile-time literal value 4 rather than computing 2+2 at run time. As you might expect there are literal versions of the standard arithmetic, logical and string operators. Thus 2*3 returns the literal value 6, rather than a run-time integer. Going beyond this, it is possible to treat each literal value as having a unique type:
proc name(x:literal(1))=”One”
proc name(x:literal(2))=”Two”
proc name(x:literal(3))=”Three”
one_two_three=name(1)++” “++name(2)++” “++name(3)
Here one_two_three is the literal string “One Two Three”.
Literal values get more powerful when combined with another PM mechanism, variable arguments. A … parameter will match any number of arguments and can in turn be passed as the last argument to a procedure call. Using this mechanism, it is possible to implement a type-secure version of a C-style printf.
proc printf(format:literal(string),a,b,…) {
printf(first_entry(format),a)
printf(remaining_entries(format),b,…)
}
printf(format:literal(string),a:int): printf_integer(format,a) check format_is_integer(format)
printf(format:literal(string),a:int): printf_real(format,a) check format_is_real(format)
// … etc
given suitable literal string functions first_entry, remaining_entries, format_is_integer, etc. The one-two-three example gives an idea about how these can be implemented.
There are also a number of compile-time reflection intrinsics. These are used extensively by the PM standard library but are being made available in the main language in PM 0.5 [exact details still being finalised]. A simplified version of structure/record value equality shows how some of these intrinsics work together:
proc ==(a:a_struct or a_rec, b:a_strunct or a_rec) {
test same_type(a,b)
_compare_elements(a,b,1,number_elements(a)-1)
}
proc _compare_elements(a,b,index:literal(int),remaining:literal(int))
= element(a,index)==element(b,index) and
_compare_elements(a,b,index+1,remaining-1)
proc _compare_elements(a,b,index:literal(int),remaining:literal(0))
= element(a,index)==element(b,index)
Here same_type checks for type equality returning a literal Boolean, number_elements gives the number of elements in a structure or record as a literal integer and element selects a given element using a literal integer index. Type specialisation [See previous post] ensures that the recursive traversal of the data structures is resolved entirely at compile time. So that’s all for now. I hope I have demonstrated how PM uses a combination of compile time types, function type matching and specialisation, variable arguments and recursion in place of macros. There is of course more to this that I have shown, including a variant on literal types that provides a form of procedure currying or forced global constant propagation. Stay tuned…
Comments
Post a Comment