Quick tour of
features. What makes
better!
Left-to-right declaration syntax, except bit fields. All declaration symbols have identical meaning, only the order (left-to-right) changes.
C∀ | C |
---|---|
* int p; // pointer to int [5] int a; // array of 5 ints * [5] int pa; // pointer to array of 5 ints [5] * int ap; // array of 5 pointers to int * int p1, p2; // distribute pointer const * const int cpc; const * [5] const int cpac; extern [5] int xa; static * const int sp; * [ int ] ( int ) fp; // pointer to function // returning int and taking int * [ * [ ] int ] ( int ) gp; [5] * [ * [ int ] (int) ] ( int ) hp; (* int)x; sizeof( [5] * int ); |
int * p; int a[5]; int (* pa)[5]; int * ap[5]; int * p1, * p2; const int * const cpc; const int * const cpac[5] extern int xa[5] static const int * sp; int (* fp)( int ) int (* (* gp)( int ))[ ]; int (* (* hp[5])( int ))( int ); (int *)x sizeof( * int [5] ); |
Multi-level rebindable references, as an alternative to pointers, to reduces syntactic noise.
int x = 1, y = 2, z = 3; int * p1 = &x, ** p2 = &p1, *** p3 = &p2, // pointers to x& r1 = x,&& r2 = r1,&&& r3 = r2; // references to x int * p4 = &z,& r4 = z; *p1 = 3; **p2 = 3; ***p3 = 3; // change x r1 = 3; r2 = 3; r3 = 3; // change x: implicit dereference *r1, **r2, ***r3 **p3 = &y; *p3 = &p4; // change p1, p2 // cancel implicit dereferences (&*)**r3, (&(&*)*)*r3, &(&*)r4& r3 = &y;&& r3 = && r4; // change r1, r2
A reference is a handle to an object, like a pointer, but is automatically dereferenced the specified number
of levels. Referencing (address-of
A C++ non-rebindable reference with null pointer checking is provided by a C∀ constant reference.
int i; int &const cr = i; // must initialized, no null pointer cr = 3; // change i through reference cr &cr = &i; // fails, assignment to read-only reference crconst int ci = 3;const int &const ccr = ci; // must initialized, no null pointer ccr = 3; // fails, assignment to read-only variable ci &ccr = &i; // fails, assignment to read-only reference ccr int &const &const crcr = cr; // must initialized, no null pointer crcr = 3; // change i through double reference crcr &crcr = &i; // fails, assignment to read-only 1st reference crcr &&crcr = &cr; // fails, assignment of read-only 2nd reference crcrconst int &const &const ccrcr = ccr; // must initialized, no null pointer ccrcr = 3; // fails, assignment of read-only variable ci &ccrcr = &ci; // fails, assignment of read-only 1st reference ccrcr &&ccrcr = &ccr; // fails, assignment of read-only 2nd reference ccrcr
Formalized lists of elements, denoted by
int i; double x, y; int f( int, int, int ); f(2, x, 3 + i ); // technically ambiguous: argument list or comma expression? f([ 2, x, 3 + i] ); // formalized (tuple) element list[ i, x, y] = 3.5; // i = 3.5, x = 3.5, y = 3.5[ x, y] =[ y, x] ; // swap values[ int, double, double] t; // tuple variable *[ int, double, double] pt = &t; // tuple pointer t =[ i + 5, x / 2.0, y * 3.1] ; // combine expressions into tuple[ i, x, y] = *pt; // expand tuple elements to variables [i,(x, y) ] = 3; // comma expression in tuple x = t.1 ; // extract 2nd tuple element (zero origin)[ y, i] = [ t.2 , t.0 ] ; // reorder and drop tuple elements pt->2 = 5.1; // change 3rd tuple element
Functions may return multiple values using tuples.
[ int, int ] div( int i, int j ) { // compute quotient, remainder return[ i / j, i % j ]; // return 2 values } int g( int, int ); // 2 parameters int main() { int quo, rem;[ quo, rem ] = div( 3, 2 ); // expand tuple elements to variables g(div( 5, 3 ) ); // expand tuple elements to parameters quo = div( 7, 2 ).0 ; // extract quotient element }
Create a tuple by selecting multiple fields by field name/index to rearrange, drop, or duplicate fields.
The aggregate expression to the left of the
[ double, float ] f( int, double ) {} struct S { char x; int y; double z; } s;s.[ x, y, z ] = 1; // select by name: s.x = 1, s.y = 1, s.z = 1s.[ y, z, x ] = [ 3, 3.2, 'x' ]; // rearrange: s.y = 3, s.z = 3.2, s.x = 'x' f(s.[ y, z ] ); // drop: f( s.y, s.z ) f(s.[ y, y ] ); // duplicate: f( s.y, s.y )s.[ 0, 1, 3 ] = 1; // select by name: s.0 = 1, s.1 = 1, s.2 = 1s.[ 1, 3, 0 ] = [ 3, 3.2, 'x' ]; // rearrange: s.1 = 3, s.3 = 3.2, s.0 = 'x' f(s.[ 1, 3 ] ); // drop: f( s.y, s.z ) f(s.[ 1, 1 ] ); // duplicate: f( s.y, s.y ) [ int, int, long, double ] x; void f( double, long ) {} f(x.[ 0, 3 ] ); // select by index: f( x.0, x.3 )x.[ 0, 1 ] =x.[ 1, 0 ] ; // [ x.0, x.1 ] = [ x.1, x.0 ] [ long, int, long ] y =x.[ 2, 0, 2 ]; struct A { double i; int j; }; struct B { int * k; short l; }; struct C { int x; A y; B z; } v;v.[ x, y.[ i, j ], z.k ]; [ int, float, double ] f() {}; [ double, float ] x =f().[ 2, 1 ]; // f() called once
Implicit initialization and de-initialization (like C++). A constructor/destructor name is denoted
by
struct VLA { int size, * data; }; // variable length array of integers void?{} ( VLA & vla ) with ( vla ) { // default constructor size = 0; data = 0p; } void?{} ( VLA & vla, int size, char fill = '\0' ) { // initialization vla.[ size, data ] = [ size, alloc( size, fill ) ]; } void?{} ( VLA & vla, VLA val ) with( val ) { // copy, deep vla.[ size, data ] = [ size, alloc( size, data ) ]; } void^?{} ( VLA & vla ) with ( vla ) { // destructor free( data ); size = 0; data = 0p; } int main() { VLA x, y = { 20, 0x01 }, z = y; // z copies y // x{}; y{ 20, 0x01 }; z{ z, y }; x{ 5 }; // allocate x ^x{}; // deallocate x ^z{}; // deallocate z z{ 5, '\xff' }; // reallocate z ^y{}; // deallocate y y{ z }; // reallocate y, copies z x{ 10, '\0' }; // reallocate x } // ^z{}; ^y{}; ^x{};
The alternative to constructor initialization is C designations, which are similar to named parameters in Python and Scala, except specific to aggregate initializers. Note C∀ designations use a colon separator, rather than C assignment, because the latter syntax conflicts with new language features.
struct A { int w, x, y, z; }; A a0 = {.x : 4,.z : 1,.x : 8 }; // A a0 = { 0, 8, 0, 1 }; A a1 = { 1,.y : 7, 6 }; // A a1 = { 1, 0, 7, 6 }; A a2[4] = {[2] : a0,[0] : a1, {.z : 3 } }; // A a2[4] = { a1, { 0, 0, 0, 3 }, a0, { 0, 0, 0, 0 } };
Any field not explicitly initialized is initialized as if it had static storage duration, implying 0 fill.
Later initializers override earlier initializers, so a field with more than one initializer is only initialized by its last initializer.
For example, in the initialization of
An enumeration is a compile-time mechanism to alias names to constants, like
enum Days { Mon, Tue, Wed, Thu, Fri, Sat, Sun }; // enumeration definition, set of 7 enumerators & constants Days days = Mon; // enumeration declaration and initialization
The set of enumerators is injected into the variable namespace at the definition scope. Hence, enumerators may be overloaded with variable, enum, and function names.
int Foo; // type/variable separate namespaces enum Foo { Bar }; enum Goo { Bar }; // overload Foo.Bar double Bar; // overload Foo.Bar, Goo.Bar
An anonymous enumeration injects enumerators with specific values into a scope.
enum { Prime = 103, BufferSize = 1024 };
which is better than using:
C preprocessor | C constant declarations |
---|---|
#define Mon 0 ... #define Sun 6 |
const int Mon = 0, ..., Sun = 6; |
because the enumeration is succinct, has automatic numbering, can appear in
The enumeration can be any type, and an enumerator's value comes from this type.
Because an enumerator is a constant, it cannot appear in a mutable context, e.g.
The default enum type is
C∀ allows other integral types with associated values.
enum() Mode { O_RDONLY, O_WRONLY, O_CREAT, O_TRUNC, O_APPEND }; enum(char ) Letter { A = 'A', B, C, I = 'I', J, K }; enum(long long int ) BigNum { X = 123_456_789_012_345, Y = 345_012_789_456_123 };
The empty type,
Non-integral enum types must be explicitly initialized, e.g.,
// non-integral numeric enum(double ) Math { PI_2 = 1.570796, PI = 3.141597, E = 2.718282 } // pointer enum(char * ) Name { Fred = "Fred", Mary = "Mary", Jane = "Jane" }; int i, j, k; enum(int * ) ptr { I = &i, J = &j, K = &k }; enum(int & ) ref { I = i, J = j, K = k }; // tuple enum([int, int] ) { T = [ 1, 2 ] }; // function void f() {...} void g() {...} enum(void (*)() ) funs { F = f, F = g }; // aggregate struct S { int i, j; }; enum(S ) s { A = { 3, 4 }, B = { 7, 8 } }; // enumeration enum(Letter ) Greek { Alph = A, Beta = B, /* more enums */ }; // alphabet intersection
Enumeration
Math m = PI; // allowed double d = PI; // allowed, conversion to base type m = E; // allowed m = Alph; //disallowed m = 3.141597; //disallowed d = m; // allowed d = Alph; //disallowed Letter l = A; // allowed Greek g = Alph; // allowed l = Alph; // allowed, conversion to base type g = A; //disallowed
A constructor cannot be used to initialize enumerators because a constructor executes at runtime.
A fallback is explicit C-style initialization using
enum( struct vec3 ) Axis { Up@= { 1, 0, 0 }, Left@= { 0, 1, 0 }, Front@= { 0, 0, 1 } }
C∀ Plan-9 inheritance may be used with enumerations.
enum( char * ) Name2 {inline Name , Jack = "Jack", Jill = "Jill" }; enum/* inferred */ Name3 {inline Name2 , Sue = "Sue", Tom = "Tom" };
Enumeration
The enum type for the inheriting type must be the same as the inherited type;
hence the enum type may be omitted for the inheriting enumeration and it is inferred from the inherited enumeration, as for
Specifically, the inheritance relationship for
Name ⊂ Name2 ⊂ Name3 ⊂ const char * // enum type of Name
For the given function prototypes, the following calls are valid.
void f( Name ); void g( Name2 ); void h( Name3 ); void j( const char * ); |
f( Fred ); g( Fred ); g( Jill ); h( Fred ); h( Jill ); h( Sue ); j( Fred ); j( Jill ); j( Sue ); j( "Will" ); |
Note, the validity of calls is the same for call-by-reference as for call-by-value, and
Nested functions and call-site inferencing provide a localized form of inheritance.
forall( T | { int ?<?( T, T ); } ) void qsort( const T * arr, size_t size ) { /* use C qsort */ } int main() { int ?<?( double x, double y ) { return x> y; } // locally override behaviour qsort( vals, 10 ); // descending sort }
The local version of
Alternative call syntax where the argument appears before the function name, where
postfix function | constant argument call | variable argument call | postfix function pointer |
---|---|---|---|
int |
0 |
int i = 7; i |
int (* |
To pass multiple arguments to a postfix function requires a tuple, e.g.,
To reduce duplication,
forall(T ) { // distribution block, add forall qualifier to declarations struct stack { // generic type T data[10]; // fixed size stack int depth; }; inline { // nested distribution block, add forall/inline to declarations void push( stack(T ) & s,T value ) { s.data[s.depth++] = value; ... } // other generic stack operations } } stack(int) si; // int stack push( si, 3 ); // inline call stack(double) sd; // double stack push( sd, 3.5 ); // inline call
Numeric literals allow underscores (like Ada/Java-8).
2_ 147_ 483_ 648; // decimal constant 56_ ul; // decimal unsigned long constant 0_ 377; // octal constant 0x_ ff_ ff; // hexadecimal constant 0x_ ef3d_ aa5c; // hexadecimal constant 3.141_ 592_ 654; // floating point constant 10_ e_ +1_ 00; // floating point constant 0x_ ff_ ff_ p_ 3; // hexadecimal floating point 0x_ 1.ffff_ ffff_ p_ 128_ l; // hexadecimal floating point long constant L_ "\x_ ff_ ee"; // wide character constant
New integral suffixes
20 |
20_ |
26 |
New pointer suffix
0p // null pointer 1p // alternative null pointer (type specific) 0x4ffffep // hardware mapped register
Literals 0 and 1 are special in C: conditional ⇒
struct S { int i, j; }; void ?{}( S & s,zero_t ) with( s ) { i = j = 0; } // zero_t, no parameter name allowed void ?{}( S & s,one_t ) with( s ) { [i, j] = 1; } // one_t, no parameter name allowed int ?!=?( S op,zero_t ) with( op ) { return i != 0 || j != 0; } S & ?+=?( S & op,one_t ) with( op ) { i += 1; j += 1; return op; } int main() { S s0 =0 , s1 =1 ; // call: ?{}( s0, zero_t ), ?{}( s1, one_t ) if (s1 ) { // rewrite: s1 => s1 != 0 => ?!=?( s1, (S){ 0 } ) s1 +=1 ; // rewrite: ?+=?( s1, (S){ 1 } ) s1++ ; // predefined polymorphic pre/post ++ call ?+=?( S &, one_t )++ s1; } }
C∀ identifies inconsistent, problematic, and missing control structures in C, and extends, modifies, and adds control structures to increase functionality and safety.
Extend
if (int x = f() ) ... // x != 0, x local to if/else statement (like C++) if (int x = f(), y = g() ) ... // x != 0 && y != 0, x and y local to if/else statement if (int x = f(), y = g(); x < y ) ... // x < y, x and y local to if/else statement if (struct S { int i; } x = { f() }; x.i < 4 ) ... // x.i < 4, S and x local to if/else statement while (int x = f() ) ... // x != 0, x local to while statement (like C++) while (int x = f(), y = g() ) ... // x != 0 && y != 0, x and y local to while statement while (int x = f(), y = g(); x < y ) ... // x and y local to while statement while (struct S { int i; } x = { f() }; x.i < 4 ) ... // x.i < 4, S and x local to while statement
Extend
switch ( i ) { case1, 3, 5 : ... // list case6~9: ... // subrange: 6, 7, 8, 9 case12~17, 21~26, 32~35: ... // list of subranges }
Extend
switch ( x ) {int i = 0; // allow declarations only at start, local to switch body case 0: ...int j = 0; // disallow, unsafe initialization case 1: {int k = 0; // allow at lower nesting levels ...case 2: // disallow, case in nested statements (no Duff's device) } ... }
Alternative
choose ( i ) { case 1, 2, 3: ...// implicit end of choose (switch break) case 5: ...fallthrough ; // explicit fall through case 7: ...break // explicit end of choose (redundant) default: j = 3; }
Allow
non-terminator | labelled | |
---|---|---|
choose ( ... ) { case 3: if ( ... ) { ... |
choose ( ... ) { case 3: ... |
choose ( ... ) { case 3: choose ( ... ) { case 4: for ( ... ) { // multi-level transfer ... |
The target label must be below the
Extend
Empty conditional implies comparison value of
while (/*empty*/ ) // while ( true ) for (/*empty*/ ) // for ( ; true; ) do ... while (/*empty*/ ) // do ... while ( true )
The
0 ~ 5 // { 0, 1, 2, 3, 4, 5 } -8 ~ -2 ~ 2 // { -8. -6, -4, -2 } -3 ~ 3 ~ 1 // { -3, -2, -1, 0, 1, 2, 3 }
The range character,
-8␣ ~ -2 // ascending, no prefix 0+ ~ 5 // ascending, prefix -3- ~ 3 // descending
For descending iteration, the
for ( i;10 -~ 1 ) // WRONG descending range!
Because C uses zero origin, most loops iterate from 0 to N - 1. Hence, when scanning a range
during iteration, the last value is dropped, e.g.,
To access the value of the range iteration in the loop body, a loop index is specified before the range.
for (int i; 0 ~ 10 ~ 2 ) { ...i ... } // loop index available in loop body
The index type is optional (like C++
for ( i;1.5 ~ 5 ) // typeof(1.5) i; 1.5 is low value for ( i;5.5 ) // typeof(5.5) i; 5.5 is high value
The following examples illustrate common \CFA
for (5 ) // for ( typeof(5) i; i < 5; i += 1 )
for (~= 5 ) // for ( typeof(5) i; i <= 5; i += 1 )
for ( 1~ 5 ) // for ( typeof(1) i = 1; i < 5; i += 1 )
for ( 1~= 5 ) // for ( typeof(1) i = 1; i <= 5; i += 1 )
for ( 1-~ 5 ) // for ( typeof(1) i = 5; i > 0; i -= 1 )
for ( 1-~= 5 ) // for ( typeof(1) i = 5; i >= 0; i -= 1 )
There are situations when the
for ( i;@ -~ 10 ) // for ( typeof(10) i = 10;/*empty*/ ; i -= 1 ) for ( i; 1 ~@ ~ 2 ) // for ( typeof(1) i = 1;/* empty */ ; i += 2 ) for ( i; 1 ~ 10 ~@ ) // for ( typeof(1) i = 1; i < 10;/* empty */ ) for ( i; 1 ~@ ~@ ) // for ( typeof(1) i = 1;/* empty */ ;/* empty */ )
There are situations when multiple loop indexes are required. The character
for ( i; 5: j; 2 ~ 12 ~ 3 ) // for ( typeof(i) i = 1, j = 2;i < 5 && j < 12 }; i += 1, j += 3 ) for ( i; 5: j; 2 ~ @ ~ 3 ) // for ( typeof(i) i = 1, j = 2;i < 5 ; i += 1, j += 3 ) for ( i; 5: j; 2.5 ~ @ ~ 3.5 ) // no C equivalent, without hoisting declaration of floating-point j
The following show more complex loop-control examples across all the different options.
while |
empty empty empty zero A A A A A A A A A A A A A A A A A A A A A A B B B B B C C C C C D D D D D E E E E E 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 10 1 3 5 7 9 10 8 6 4 2 0.5 1.5 2.5 3.5 4.5 5.5 4.5 3.5 2.5 1.5 2 4 6 8 10 10 8 6 4 2 N N N N N N N N N N 0 1 2 3 4 5 6 7 8 9 10 9 8 7 6 5 4 3 2 1 3 6 9 1 2 3 4 5 6 7 8 9 10 2.1 3.8 5.5 7.2 8.9 0 -5 1 -4 2 -3 3 -2 4 -1 |
Extend
Compound: {Try: try {For: for ( ... ) {While: while ( ... ) {Do: do {If: if ( ... ) {Switch: switch ( ... ) { case 3:break Compound ;break Try ;break For ; /* or */continue For ;break While ; /* or */continue While ;break Do ; /* or */continue Do ;break If ;break Switch ; } // switch } else { ...break If ; ... // terminate if } // if } while ( ... ); // do } // while } // for }finally { // always executed } // try } // compound
The
The following example is a linear search for the key 3 in an array, where finding the key is handled with a
int a[10]; while ( int i = 0; i < 10 ) { if ( a[i] == 3 ) break; // found i += 1; } |
for ( i; 10 ) { if ( a[i] == 3 ) break; // found } |
int i = 0; do { if ( a[i] == 3 ) break; // found i += 1; } while( i < 10 ) |
Exception handling provides dynamic name look-up and non-local transfer of control.
exception_t E {}; // exception type void f(...) { ...throw E{}; ... // termination ...throwResume E{}; ... // resumption }try { f(...); }catch( E e ; boolean-predicate ) { // termination handler, optional predicate // recover and continue }catchResume( E e ; boolean-predicate ) { // resumption handler, optional predicate // repair and return }finally { // always executed }
Open an aggregate scope making its fields directly accessible (like Pascal, but open in parallel).
struct S { inti ; int j; double m; } s; // field i has same type in structure types S and T struct T { inti ; int k; int m; } t; with ( s, t ) { // open structure variables s and t in parallel j + k; // unambiguous, s.j + t.k m = 5.0; // unambiguous, s.m = 5.0 m = 1; // unambiguous, t.m = 1 int a = m; // unambiguous, a = t.m double b = m; // unambiguous, b = s.m int c = s.i + t.i; // unambiguous with qualification (double)m; // unambiguous with cast s.m }
C∀'s ability to overload variables means fields with the same name but different types are automatically disambiguated, eliminating most qualification when opening multiple aggregates. Qualification or a cast is used to disambiguate.
Local acquisition of monitor mutual exclusion to reduce refactoring and naming (like Java).
monitor ReadersWriter { ... }; void startRead( ReadersWriter & mutex rw ) { // critical section } void endRead( ReadersWriter & mutex rw ) { // critical section } void read( ReadersWriter & rw ) { // no mutex startRead( rw ); // multiple readers endRead( rw ); } |
void read( ReadersWriter & rw ) { // no mutex |
As for monitors functions, a list of monitor objects is allowed and acquired without deadlock.
The special form of the
mutex(sout )sout | ...; // serialize printing mutex(a, b, c ) [a, b, c] = ... // acquire multiple kinds of locking objects without deadlock
Dynamic selection of calls to mutex type is controlled by the
void main() { waitfor( r1 : c ) ...; waitfor( r1 : c ) ...; or waitfor( r2 : c ) ...; waitfor( r2 : c ) { ... } or timeout( 1 ) ...; waitfor( r3 : c1, c2 ) ...; or else ...; when( a > b ) waitfor( r4 : c ) ...; or when ( c > d ) timeout( 2 ) ...; or when ( c > 5 ) else ...; when( a > b ) waitfor( r5 : c1, c2 ) ...; or waitfor( r6 : c1 ) ...; or else ...; when( a > b ) waitfor( r7 : c ) ...; or waitfor( r8 : c ) ...; or timeout( 2 ) ...; when( a > b ) waitfor( r8 : c ) ...; or waitfor( r9 : c1, c2 ) ...; or when ( c > d ) timeout( 1 ) ...; or else ...; }
Specifically, a thread calling the monitor is unblocked directly from the calling queue based on function names that can fulfill the cooperation required by the signaller.
(The linear search through the calling queue to locate a particular call can be reduced to O(1).)
Hence, the
Variables, functions, operators, and literals 0/1 may be overloaded.
Variable names within a block may be overloaded depending on type.
short int MAX = 32767; int MAX = 2147483647; double MAX = 1.797...357e+308L; |
short int s = MAX; // select MAX based on left-hand type int i = MAX; double d = MAX; |
Function names within a block may be overloaded depending on the number and type of parameters and returns.
// select based on type and number of parameters void f( |
// select based on type and number of returns |
Operator names within a block may be overloaded depending on the number and type of parameters and returns.
An operator name is denoted with
int++? ( int & op ); // unary prefix increment int?++ ( int & op ); // unary postfix increment int?+? ( int op1, int op2 ); // binary plus int?<=? ( int op1, int op2 ); // binary less than int?=? ( int & op1, int op2 ); // binary assignment int?+=? ( int & op1, int op2 ); // binary plus-assignment struct S { int i, j; }; S?+? ( S op1, S op2 ) { // add two structures return (S){ op1.i + op2.i, op1.j + op2.j }; } S s1 = { 1, 2 }, s2 = { 2, 3 }, s3; s3 = s1+ s2; // compute sum: s3 == { 2, 5 }
Existing C library-types can be augmented without inheritance. C++ can only partially extend C types as
constructors, destructors, conversions and operators
#include <time.h> void ?{}( timespec & t ) {} void ?{}( timespec & t, time_t sec ) with(t) { tv_sec = sec; tv_nsec = 0; } void ?{}( timespec & t, time_t sec, time_t nsec ) with(t) { tv_sec = sec; tv_nsec = nsec; } void ?{}( timespec & t, zero_t ) with(t) { tv_sec = 0; tv_nsec = 0; } timespec ?+?( timespec & lhs, timespec rhs ) with(lhs) { return (timespec)@{ tv_sec + rhs.tv_sec, tv_nsec + rhs.tv_nsec }; // @ ⇒ C-style initialization } _Bool ?==?( timespec lhs, timespec rhs ) with(lhs) { return tv_sec == rhs.tv_sec && tv_nsec == rhs.tv_nsec; } timespec ?=?( timespec & t, zero_t ) { return t{ 0 }; } timespec tv0, tv1 =0 , tv2 ={ 3 } , tv3 ={ 5, 100000 } ; tv0 = tv1; tv1 = tv2+ tv3; if ( tv2== tv3 ) ... tv3 = tv2 =0 ;
Functions and aggregate type may have multiple type parameters each with constraints. Aggregate types may have single type inheritance and multiple implementation inheritance.
There are 5 kinds of polymorphism type.
forall(T & ) void f( T & p ) { // incomplete type, no data alignment or size T & t; // no constructor T & s = { p }; // pointer initialization &t = &p; // pointer assignmentt = p; // no data assignment }
forall(T * ) void f( T * p ) { // incomplete type, data alignment and size T * t; // no constructor T * s = { p }; // pointer initialization t = p; // pointer assignment *t = *p; // data assignment } forall( T * ) apply( void (*f)( T * ), unsigned size, T data[size] ) { // C style array for ( unsigned i = 0 ; i < size ; i += 1 ) { f( &data[index] ); // subscript computation needs sizeof(T) } }
forall(T ) void f( T p ) { // complete type, data alignment and size T t; // default constructor T s = { p }; // copy constructor t = p; // assignment } // destructor int i; // complete type f( i ); struct S { int f; } s; // complete type f( s ); forall( T ) struct G { T f; }; // generic type G(int) g; // complete type f( g );
forall( R, S ) struct pair {R r; S s;}; forall( T *,TheParams ... // parameter pack | { void ?{}( T &, Params ); } ) T * new( Params p ) { return & (*(T *) malloc()){ p }; } pair(int, char) * x = new(42, '!' ); // argument pack
forall( T,[N] ) struct array { // => element type, dimension // use T and [N] }; array( int, 5 ) arr; // int arr[5] array( double, 100 ) arr; // double arr[100] int i = arr[3]; // subscript checking double d = arr[3]; forall(T ,[N] ) void f( array( T, N ) x, array( T, N ) y ) { ... } forall(T ,[N] ,[M] ) void g( array( T, N ) x, array( T, M ) y ) { ... } forall(T ,S ,[N] ,[M] ) void h( array( T, N ) x, array( S, M ) y ) { ... } ... f( a, b ); // a,b same type and dimension g( a, b ); // a,b same type and different dimensions h( a, b ); // a,b different types and dimensions // length accessible at runtime inf ,g , andh (like Java.length) . int pivot =N / 2; for ( i;M ) sout | a[i];
Named collection of constraints, both functions and variables.
forall( T ) trait sumable { void ?{}( T &, zero_t ); // constructor from 0 literal T ?+?( T, T ); // assortment of additions T ?+=?( T &, T ); T ++?( T & ); T ?++( T & ); };
Functions may have multiple type parameters each with constraints.
forall( T | sumable( T ) ) // polymorphic, use trait T sum( T a[ ], size_t size ) {T total =0 ; // instantiate T from 0 by calling its constructor for ( i; size ) total+= a[i]; // select appropriate + return total; } int sa[ 5 ]; int i = sum( sa, 5 ); // use int 0 and +=
Aggregate types may have multiple type parameters each with constraints.
forall( T | sumable( T ) ) { // forall distribution struct TwoArrays { // polymorphic (generic) type *T x, y; // two C arrays, new declaration syntax }; void ?{}( TwoArrays(T ) & ta, int size ) with ( ta ) { // constructor x = alloc( size ); y = alloc( size ); } void ^?{}( TwoArrays(T ) & ta ) with ( ta ) { // destructor free( x ); free( y ); }} int main() { enum { size = 5 }; TwoArrays(int ) tai = { size }; TwoArrays(double ) tad = { size }; for ( i; size ) { tai.x[i] = i; tad.y[i] = i; } // initialize sout | sum( tai.x, size ) | sum( tad.y, size ); // select int and double sum }
Multiple implementation/nominal inheritance based on Plan-9 C. No down-casting from derived to base type.
struct S0 { int i, j; }; struct S1 { int i, j; }; struct S2 {inline S1; // implementation/nominal inheritance int i, j;inline S0; // implementation/nominal inheritance }; void f1( S0 s1 ) with(s1) { sout | i | j; } void f2( S1 s0 ) with(s0) { sout | i | j; } void f3( S2 s2 ) { sout | s2.i | s2.j; S0 s0 =s2 ; // (S0)s2 with(s0) { sout | i | j; } S1 & s1 =s2 ; // (S1)s2 with(s1) { sout | i | j; } } int main() { // S1 i, j S0 S2 s2 = { { 1, 2 }, 3, 4, { 5, 6 } }; f1(s2 ); // (S0)s2 f2(s2 ); // (S1)s2 f3( s2 ); } $ a.out 5 6 1 2 3 4 5 6 1 2
Auto type-inferencing occurs in a declaration where a variable's type is inferred from its initialization expression type.
auto j = 3.0 * 4; int i; auto k = i; |
#define expr 3.0 * i typeof(expr) j = expr; // use type of initialization expression int i; typeof(i) k = i; // use type of primary variable |
The two important capabilities are:
In C∀,
Finally,
auto j =...
and the need to write a function to compute using
void rtn(... parm ); rtn( j );
A programmer must somehow work backwards to determine the type of
There is also the conundrum in type inferencing of when to brand a type. That is, when is the type of the variable more important than the type of its initialization expression. For example, if a change is made in an initialization expression, it can cause cascading type changes and/or errors. At some point, a variable type needs to remain constant and the expression to be in error when it changes.
Given
Generators, coroutines, monitors, and threads provide advanced typed control-flow, similar to typed control-flow in μC++.
Stackless asymmetric and symmetric generators allow retaining data and execution state between calls.
Unlike asynchronous await, C∀ generators are programmer scheduled.
State is only retained in the generator type but not the generator's
generator Fib { int fn1, fn; }; void main( Fib & b ) with(b) { [fn1, fn] = [1, 0]; for () {suspend; [fn1, fn] = [fn, fn + fn1]; } } int main() { Fib f1, f2; for ( 10 ) {resume( f1 ); resume( f2 ); sout | f1.fn | f2.fn; } }
Stackful asymmetric and symmetric coroutines allow retaining data and execution state between calls.
Unlike asynchronous await, C∀ coroutines are programmer scheduled.
State is retained in both the coroutine type and the coroutine's
#include <fstream.hfa> #include<coroutine.hfa> // match left/right parenthesis: ((())) match, (() mismatch enum Status { Cont, Match, Mismatch };coroutine CntParens { char ch; // used for communication Status status; }; voidmain ( CntParens & cpns ) with( cpns ) { // coroutine main unsigned int cnt = 0; for ( ; ch == '('; cnt += 1 )suspend; // count left parenthesis for ( ; ch == ')' && cnt > 1; cnt -= 1 )suspend; // count right parenthesis status = ch == ')' ? Match : Mismatch; } void ?{}( CntParens & cpns ) with( cpns ) { status = Cont; } Status next( CntParens & cpns, char c ) with( cpns ) { // coroutine interface ch = c;resume( cpns ); return status; } int main() { CntParens cpns; char ch; for () { // read until end of file sin | ch; // read one character if ( eof( sin ) ) { sout | "Mismatch"; break; } // eof ? Status ret = next( cpns, ch ); // push character for checking if ( ret == Match ) { sout | "Match"; break; } if ( ret == Mismatch ) { sout | "Mismatch"; break; } } }
A
Bank transfer problem, where two resources must be locked simultaneously.
C∀ | C++ |
---|---|
#include <thread.hfa> |
#include <thread> #include <mutex> using namespace std; struct BankAccount { |
A thread type, T, instance creates a user-level thread, which starts running in function
#include <fstream.hfa> #include <thread.hfa>thread T { int id; }; void ?{}( T & t ) { t.id = 0; } void ?{}( T & t, int id ) { t.id = id; }void main( T & t ) with( t ) { // thread starts here sout | id; } int main() { enum { NumThreads = 5 }; Tt[ NumThreads ] ; // create/start threads T * tp[ NumThreads ]; for ( i; NumThreads ) { tp[i] =new( i + 1 ) ; // create/start threads } for ( i; NumThreads ) {delete( tp[i] ); // wait for thread to terminate }} // wait for threads to terminate
C∀ provides polymorphic stream I/O via
#include <fstream.hfa> // C∀ stream I/O char c; int i; double d; sin| c| i| d; // input format depends on variable type: x 27 2.3 sout| c| i| d; // output format depends on constant/variable type x 27 2.3 // implicit separation between values and auto newline [int, [ int, int ] ] t1 = [ 1, [ 2, 3 ] ], t2 = [ 4, [ 5, 6 ] ]; sout| t1| t2; // print tuples 1, 2, 3 4, 5, 6
Polymorphic streams
exit | "x (" | x | ") has negative value."; // terminate and return EXIT_FAILURE to shellabort | "x (" | x | ") has negative value."; // terminate and generate stack trace and core file
Note, C∀ stream variables
The following example is idiomatic C∀ command-line processing and copying an input file to an output file. Note, a stream variable may be copied because it is a reference to an underlying stream data-structures. All unusual I/O cases are handled as exceptions, including end-of-file.
#include<fstream.hfa> int main( int argc, char * argv[] ) {ifstream in = stdin; // copy default filesofstream out = stdout; try { choose ( argc ) { case 3, 2:open ( in, argv[1] ); // open input file first as output creates file if ( argc == 3 )open ( out, argv[2] ); // do not create output unless input opens case 1: ; // use default files default:exit | "Usage" | argv[0] | "[ input-file (default stdin) [ output-file (default stdout) ] ]"; } // choose } catch(open_failure * ex; ex->istream == &in ) { // input file errorsexit | "Unable to open input file" | argv[1]; } catch(open_failure * ex; ex->ostream == &out ) { // output file errorsclose ( in ); // optionalexit | "Unable to open output file" | argv[2]; } // try out | nlOff; // turn off auto newline in | nlOn; // turn on reading newline char ch; try { for () { // read/write characters in | ch; out | ch; } // for } catch( end_of_file * ) { // end-of-file raised } // try } // main
The stream types ostrstream and istrstream provide all the stream formatting capabilities to/from a C string rather than a stream file. The only string stream operations different from a file stream are:
void ?{}( ostrstream &, char buf[], size_t size ); void ?{}( istrstream & is, char buf[] );
ostrstream & write( ostrstream & os, FILE * stream = stdout );There is no
There is a sequential PRNG type, only accessible by a single thread (not thread-safe), and a set of global and companion thread PRNG functions, accessible by multiple threads without contention.
struct PRNG { ... }; // opaque type void ?{}( PRNG & prng ); // random seed void ?{}( PRNG & prng, uint32_t seed ); // fixed seed void set_seed( PRNG & prng, uint32_t seed ); // set seed uint32_t get_seed( PRNG & prng ); // get seed uint32_t prng( PRNG & prng ); // [0,UINT_MAX] uint32_t prng( PRNG & prng, uint32_t u ); // [0,u) uint32_t prng( PRNG & prng, uint32_t l, uint32_t u ); // [l,u] uint32_t calls( PRNG & prng ); // number of calls
Sequential execution is repeatable given the same starting seeds.
The following shows an example that creates two sequential
PRNG prng1, prng2;set_seed( prng1, 1009 ) ;set_seed( prng2, 1009 ) ; for ( 10 ) { // Do not cascade prng calls because side-effect functions called in arbitrary order. sout | nlOff |prng( prng1 ) ; sout |prng( prng1, 5 ) ; sout |prng( prng1, 0, 5 ) | '\t'; sout |prng( prng2 ) ; sout |prng( prng2, 5 ) ; sout |prng( prng2, 0, 5 ) | nlOn; } 37301721 2 2 37301721 2 2 1681308562 1 3 1681308562 1 3 290112364 3 2 290112364 3 2 1852700364 4 3 1852700364 4 3 733221210 1 3 733221210 1 3 1775396023 2 3 1775396023 2 3 123981445 2 3 123981445 2 3 2062557687 2 0 2062557687 2 0 283934808 1 0 283934808 1 0 672325890 1 3 672325890 1 3
The PRNG global and companion thread functions are for concurrent programming, such as randomizing
execution in short-running programs, e.g.,
void set_seed( uint32_t seed ); // set global seed uint32_t get_seed(); // get global seed // SLOWER uint32_t prng(); // [0,UINT_MAX] uint32_t prng( uint32_t u ); // [0,u) uint32_t prng( uint32_t l, uint32_t u ); // [l,u] // FASTER uint32_t prng( thread$ & th ); // [0,UINT_MAX] uint32_t prng( thread$ & th, uint32_t u ); // [0,u) uint32_t prng( thread$ & th, uint32_t l, uint32_t u ); // [l,u]
The only difference between the two sets of
Because concurrent execution is non-deterministic, seeding the concurrent PRNG is less important, as
repeatable execution is impossible. Hence, there is one system-wide PRNG (global seed) but each
C∀ thread has its own non-contended PRNG state. If the global seed is set, threads start with
this seed, until it is reset and than threads start with the reset seed. Hence, these threads generate
the same sequence of random numbers from their specific starting seed. If the global seed
is not set, threads start with a random seed, until the global seed is set. Hence, these
threads generate different sequences of random numbers. If each thread needs its own seed, use a
sequential
The following shows an example using the slower/faster concurrent PRNG in the program main and a thread.
thread T {}; void main(T & th ) { // thread address for ( i; 10 ) { sout | nlOff |prng() ; sout |prng( 5 ) ; sout |prng( 0, 5 ) | '\t'; // SLOWER sout | nlOff |prng( th ) ; sout |prng( th, 5 ) ; sout |prng( th, 0, 5 ) | nlOn; // FASTER } } int main() { set_seed( 1009 );thread$ & th = *active_thread() ; // program-main thread-address for ( i; 10 ) { sout | nlOff |prng() ; sout |prng( 5 ) ; sout |prng( 0, 5 ) | '\t'; // SLOWER sout | nlOff |prng( th ) ; sout |prng( th, 5 ) ; sout |prng( th, 0, 5 ) | nlOn; // FASTER } sout | nl; T t; // run thread } 37301721 2 2 1681308562 1 3 1681308562 1 3 1852700364 4 3 290112364 3 2 1775396023 2 3 1852700364 4 3 2062557687 2 0 733221210 1 3 672325890 1 3 1775396023 2 3 873424536 3 4 123981445 2 3 866783532 0 1 2062557687 2 0 17310256 2 5 283934808 1 0 492964499 0 0 672325890 1 3 2143013105 3 2 // same output as above from thread t
Interface to GMP multi-precise library through type
C∀ | C |
---|---|
#include |
#include |
Large constants are created using numerical strings.
Int x = {"50000000000000000000" }; x ="12345678901234567890123456789"`mp +"12345678901234567890123456789"`mp ;
Keywords as identifier to deal with new keyword clashes in legacy code.
int`` int,`` forall; // keywords as identifiers`` forall =`` int = 5;`` int += 7;
New binary exponentiation operator
1\ 0; // integral result, 1 convention 1\ 1; // integral result, 1 2\ 8; // integral result (shifting), 256 -4\ 3; // integral result (multiplication), -64 5\ 3; // integral result (multiplication), 125 5\ 32; // integral result (multiplication), 0 overflow 5L\ 32; // integral result (multiplication), 3273344365508751233 5L\ 64; // integral result (multiplication), 0 overflow -4\ -3; // integral result (multiplication), 0 reciprocal fraction -4.0\ -3; // floating-point result (logarithm), -0.015625 reciprocal fraction 4.0\ 2.1; // floating-point result (logarithm), 18.3791736799526 (1.0f+2.0fi)\ (3.0f+2.0fi); // complex floating-point result (logarithm), 0.264715-1.1922i
Keywords
struct S { ... }; enum E { ... }; S s; // "struct" before S unnecessary E e; // "enum" before E unnecessary
#include <fstream.hfa> int main() {char c = 'a';signed char sc = 'a';unsigned char uc = 'a'; sout | c | sc | uc; // prints a97 97 }
New basic overloadable type for 128-bit integers.
int main() {int128 wi = 1;unsigned int128 uwi = 2; wi += uwi; }
Pseudo function like
basetypeof( const unsigned int ) usi; // unsigned int volatile const signed char ch;basetypeof( ch ) sc; // signed char enum { N = 3 };basetypeof( N ) si; // signed int
C∀ introduces the following new keywords, which cannot be used as identifiers without backquotes.
basetypeof choose coroutine disable |
enable exception fallthrough fallthru |
finally fixup forall generator |
int128 monitor mutex one_t |
report suspend throw throwResume |
trait try virtual waitfor |
when with zero_t |
C∀ introduces the following new quasi-keywords, which can be used as identifiers.
catch catchResume finally |
fixup or timeout |