# Changeset a0fc78a

Ignore:
Timestamp:
Apr 11, 2017, 4:20:46 PM (6 years ago)
Branches:
aaron-thesis, arm-eh, cleanup-dtors, deferred_resn, demangler, enum, forall-pointer-decay, jacob/cs343-translation, jenkins-sandbox, master, new-ast, new-ast-unique-expr, new-env, no_list, persistent-indexer, pthread-emulation, qualifiedEnum, resolv-new, with_gc
Children:
f674479
Parents:
b39e3dae
Message:

compress up to page 6

File:
1 edited

### Legend:

Unmodified
 rb39e3dae & 2017  & 2012  & 2007  & 2002  & 1997  & 1992  & 1987          \\ \hline Java    & 1             & 1             & 1             & 3             & 13    & -             & -                     \\ Java    & 1             & 1             & 1             & 1             & 12    & -             & -                     \\ \hline \Textbf{C}      & \Textbf{2}& \Textbf{2}& \Textbf{2}& \Textbf{1}& \Textbf{1}& \Textbf{1}& \Textbf{1}    \\ \Textbf{C}      & \Textbf{2}& \Textbf{2}& \Textbf{2}& \Textbf{2}& \Textbf{1}& \Textbf{1}& \Textbf{1}    \\ \hline \CC             & 3             & 3             & 3             & 3             & 2             & 2             & 4                     \\ int val = twice( twice( 3.7 ) ); \end{lstlisting} which works for any type @T@ with a matching addition operator. The polymorphism is achieved by creating a wrapper function for calling @+@ with @T@ bound to @double@, then passing this function to the first call of @twice@. There is now the option of using the same @twice@ and converting the result to @int@ on assignment, or creating another @twice@ with type parameter @T@ bound to @int@ because \CFA uses the return type (as in~\cite{Ada}) in its type analysis. The first approach has a late conversion from @int@ to @double@ on the final assignment, while the second has an eager conversion to @int@. \CFA minimizes the number of conversions and their potential to lose information, so it selects the first approach, which corresponds with C-programmer intuition. which works for any type @T@ with a matching addition operator. The polymorphism is achieved by creating a wrapper function for calling @+@ with @T@ bound to @double@, then passing this function to the first call of @twice@. There is now the option of using the same @twice@ and converting the result to @int@ on assignment, or creating another @twice@ with type parameter @T@ bound to @int@ because \CFA uses the return type (as in~\cite{Ada}) in its type analysis. The first approach has a late conversion from @double@ to @int@ on the final assignment, while the second has an eager conversion to @int@. \CFA minimizes the number of conversions and their potential to lose information, so it selects the first approach, which corresponds with C-programmer intuition. Crucial to the design of a new programming language are the libraries to access thousands of external software features. int posn = bsearch( 5.0, vals, 10 ); \end{lstlisting} The nested routine @comp@ (impossible in \CC as lambdas do not use C calling conventions) provides the hidden interface from typed \CFA to untyped (@void *@) C, plus the cast of the result. The nested routine @comp@ provides the hidden interface from typed \CFA to untyped (@void *@) C, plus the cast of the result. Providing a hidden @comp@ routine in \CC is awkward as lambdas do not use C calling-conventions and template declarations cannot appear at block scope. As well, an alternate kind of return is made available: position versus pointer to found element. \CC's type-system cannot disambiguate between the two versions of @bsearch@ because it does not use the return type in overload resolution, nor can \CC separately compile a templated @bsearch@. return total; } \end{lstlisting} A trait name plays no part in type equivalence; it is solely a macro for a list of assertions. Traits may overlap assertions without conflict, and therefore, do not form a hierarchy. In fact, the set of operators is incomplete, \eg no assignment, but @otype@ is syntactic sugar for the following implicit trait: % \end{lstlisting} Traits may be used for many of the same purposes as interfaces in Java or abstract base classes in \CC. Unlike Java interfaces or \CC base classes, \CFA types do not explicitly state any inheritance relationship to traits they satisfy, which is a form of structural inheritance, similar to the implementation of an interface in Go~\citep{Go}, as opposed to the nominal inheritance model of Java and \CC. Nominal inheritance can be simulated with traits using marker variables or functions: \begin{lstlisting} trait nominal(otype T) { T is_nominal; The \CFA type-system uses \emph{nominal typing} for concrete types, matching with the C type-system. and \emph{structural typing} for polymorphic types. Hence, trait names play no part in type equivalence; the names are simply macros for a list of polymorphic assertions, which are expanded at usage sites. Nevertheless, trait names form a logical subtype-hierarchy with @dtype@ at the top, where traits often contain overlapping assertions. Traits are used like interfaces in Java or abstract base-classes in \CC, but without the nominal inheritance-relationships. Instead, each polymorphic function (or generic type) defines the structural type needed for its execution (polymorphic type-key), and this key is fulfilled at each call site from the lexical environment, which is similar to Go~\citep{Go} interfaces. Hence, new lexical scopes and nested functions are used extensively to create local subtypes, as in the @qsort@ example, without having to manage a nominal-inheritance hierarchy. (Nominal inheritance can be approximated with traits using marker variables or functions, as is done in Go.) % Nominal inheritance can be simulated with traits using marker variables or functions: % \begin{lstlisting} % trait nominal(otype T) { %     T is_nominal; % }; % int is_nominal;                                                               $\C{// int now satisfies the nominal trait}$ % \end{lstlisting} % % Traits, however, are significantly more powerful than nominal-inheritance interfaces; most notably, traits may be used to declare a relationship \emph{among} multiple types, a property that may be difficult or impossible to represent in nominal-inheritance type systems: % \begin{lstlisting} % trait pointer_like(otype Ptr, otype El) { %     lvalue El *?(Ptr);                                                $\C{// Ptr can be dereferenced into a modifiable value of type El}$ % } % struct list { %     int value; %     list *next;                                                               $\C{// may omit "struct" on type names as in \CC}$ % }; % typedef list *list_iterator; % % lvalue int *?( list_iterator it ) { return it->value; } % \end{lstlisting} % In the example above, @(list_iterator, int)@ satisfies @pointer_like@ by the user-defined dereference function, and @(list_iterator, list)@ also satisfies @pointer_like@ by the built-in dereference operator for pointers. Given a declaration @list_iterator it@, @*it@ can be either an @int@ or a @list@, with the meaning disambiguated by context (\eg @int x = *it;@ interprets @*it@ as an @int@, while @(*it).value = 42;@ interprets @*it@ as a @list@). % While a nominal-inheritance system with associated types could model one of those two relationships by making @El@ an associated type of @Ptr@ in the @pointer_like@ implementation, few such systems could model both relationships simultaneously. \section{Generic Types} One of the known shortcomings of standard C is that it does not provide reusable type-safe abstractions for generic data structures and algorithms. Broadly speaking, there are three approaches to create data structures in C. One approach is to write bespoke data structures for each context in which they are needed. While this approach is flexible and supports integration with the C type-checker and tooling, it is also tedious and error-prone, especially for more complex data structures. A second approach is to use @void *@--based polymorphism. This approach is taken by the C standard library functions @bsearch@ and @qsort@, and does allow the use of common code for common functionality. However, basing all polymorphism on @void *@ eliminates the type-checker's ability to ensure that argument types are properly matched, often requiring a number of extra function parameters, pointer indirection, and dynamic allocation that would not otherwise be needed. A third approach to generic code is to use pre-processor macros, which does allow the generated code to be both generic and type-checked, but errors may be difficult to interpret. Furthermore, writing and using preprocessor macros can be unnatural and inflexible. Other languages use \emph{generic types}, \eg \CC and Java, to produce type-safe abstract data-types. \CFA also implements generic types that integrate efficiently and naturally with the existing polymorphic functions, while retaining backwards compatibility with C and providing separate compilation. However, for known concrete parameters, the generic type can be inlined, like \CC templates. A generic type can be declared by placing a @forall@ specifier on a @struct@ or @union@ declaration, and instantiated using a parenthesized list of types after the type name: \begin{lstlisting} forall( otype R, otype S ) struct pair { R first; S second; }; int is_nominal;                                                         $\C{// int now satisfies the nominal trait}$ \end{lstlisting} Traits, however, are significantly more powerful than nominal-inheritance interfaces; most notably, traits may be used to declare a relationship \emph{among} multiple types, a property that may be difficult or impossible to represent in nominal-inheritance type systems: \begin{lstlisting} trait pointer_like(otype Ptr, otype El) { lvalue El *?(Ptr);                                          $\C{// Ptr can be dereferenced into a modifiable value of type El}$ } struct list { int value; list *next;                                                         $\C{// may omit "struct" on type names as in \CC}$ }; typedef list *list_iterator; lvalue int *?( list_iterator it ) { return it->value; } \end{lstlisting} In the example above, @(list_iterator, int)@ satisfies @pointer_like@ by the user-defined dereference function, and @(list_iterator, list)@ also satisfies @pointer_like@ by the built-in dereference operator for pointers. Given a declaration @list_iterator it@, @*it@ can be either an @int@ or a @list@, with the meaning disambiguated by context (\eg @int x = *it;@ interprets @*it@ as an @int@, while @(*it).value = 42;@ interprets @*it@ as a @list@). While a nominal-inheritance system with associated types could model one of those two relationships by making @El@ an associated type of @Ptr@ in the @pointer_like@ implementation, few such systems could model both relationships simultaneously. \section{Generic Types} One of the known shortcomings of standard C is that it does not provide reusable type-safe abstractions for generic data structures and algorithms. Broadly speaking, there are three approaches to create data structures in C. One approach is to write bespoke data structures for each context in which they are needed. While this approach is flexible and supports integration with the C type-checker and tooling, it is also tedious and error-prone, especially for more complex data structures. A second approach is to use @void*@-based polymorphism. This approach is taken by the C standard library functions @qsort@ and @bsearch@, and does allow the use of common code for common functionality. However, basing all polymorphism on @void*@ eliminates the type-checker's ability to ensure that argument types are properly matched, often requires a number of extra function parameters, and also adds pointer indirection and dynamic allocation to algorithms and data structures that would not otherwise require them. A third approach to generic code is to use pre-processor macros to generate it -- this approach does allow the generated code to be both generic and type-checked, though any errors produced may be difficult to interpret. Furthermore, writing and invoking C code as preprocessor macros is unnatural and somewhat inflexible. Other C-like languages such as \CC and Java use \emph{generic types} to produce type-safe abstract data types. \CFA implements generic types with some care taken that the generic types design for \CFA integrates efficiently and naturally with the existing polymorphic functions in \CFA while retaining backwards compatibility with C; maintaining separate compilation is a particularly important constraint on the design. However, where the concrete parameters of the generic type are known, there is no extra overhead for the use of a generic type, as for \CC templates. A generic type can be declared by placing a @forall@ specifier on a @struct@ or @union@ declaration, and instantiated using a parenthesized list of types after the type name: \begin{lstlisting} forall(otype R, otype S) struct pair { R first; S second; }; forall(otype T) T value( pair(const char*, T) p ) { return p.second; } forall(dtype F, otype T) T value_p( pair(F*, T*) p ) { return *p.second; } pair(const char*, int) p = { "magic", 42 }; forall( otype T ) T value( pair( const char *, T ) p ) { return p.second; } forall( dtype F, otype T ) T value_p( pair( F *, T * ) p ) { return *p.second; } pair( const char *, int ) p = { "magic", 42 }; int magic = value( p ); pair(void*, int*) q = { 0, &p.second }; pair( void *, int * ) q = { 0, &p.second }; magic = value_p( q ); double d = 1.0; pair(double*, double*) r = { &d, &d }; pair( double *, double * ) r = { &d, &d }; d = value_p( r ); \end{lstlisting} \CFA classifies generic types as either \emph{concrete} or \emph{dynamic}. Concrete generic types have a fixed memory layout regardless of type parameters, while dynamic generic types vary in their in-memory layout depending on their type parameters. A type may have polymorphic parameters but still be concrete; in \CFA such types are called \emph{dtype-static}. Polymorphic pointers are an example of dtype-static types -- @forall(dtype T) T*@ is a polymorphic type, but for any @T@ chosen, @T*@ has exactly the same in-memory representation as a @void*@, and can therefore be represented by a @void*@ in code generation. \CFA generic types may also specify constraints on their argument type to be checked by the compiler. For example, consider the following declaration of a sorted set-type, which ensures that the set key supports equality and relational comparison: \begin{lstlisting} forall(otype Key | { _Bool ?==?(Key, Key); _Bool ?first, b->first); if ( c == 0 ) c = cmp(a->second, b->second); The reuse of dtype-static structure instantiations enables useful programming patterns at zero runtime cost. The most important such pattern is using @forall(dtype T) T *@ as a type-checked replacement for @void *@, \eg creating a lexicographic comparison for pairs of pointers used by @bsearch@ or @qsort@: \begin{lstlisting} forall(dtype T) int lexcmp( pair( T *, T *) * a, pair( T *, T * ) * b, int (* cmp)( T *, T * ) ) { int c = cmp( a->first, b->first ); if ( c == 0 ) c = cmp( a->second, b->second ); return c; } \end{lstlisting} Since @pair(T*, T*)@ is a concrete type, there are no added implicit parameters to @lexcmp@, so the code generated by \CFA is effectively identical to a version of this function written in standard C using @void*@, yet the \CFA version is type-checked to ensure that the fields of both pairs and the arguments to the comparison function match in type. Another useful pattern enabled by reused dtype-static type instantiations is zero-cost tag'' structs. Sometimes a particular bit of information is only useful for type-checking, and can be omitted at runtime. Tag structs can be used to provide this information to the compiler without further runtime overhead, as in the following example: Since @pair(T *, T *)@ is a concrete type, there are no implicit parameters passed to @lexcmp@, so the generated code is identical to a function written in standard C using @void *@, yet the \CFA version is type-checked to ensure the fields of both pairs and the arguments to the comparison function match in type. Another useful pattern enabled by reused dtype-static type instantiations is zero-cost tag'' structures. Sometimes a particular bit of information is only useful for type-checking, and can be omitted at runtime. Tag structs can be used to provide this information to the compiler without further runtime overhead, as in the following example: \begin{lstlisting} forall(dtype Unit) struct scalar { unsigned long value; }; struct litres {}; forall(dtype U) scalar(U) ?+?(scalar(U) a, scalar(U) b) { forall(dtype U) scalar(U) ?+?( scalar(U) a, scalar(U) b ) { return (scalar(U)){ a.value + b.value }; } scalar(metres) marathon = half_marathon + half_marathon; scalar(litres) two_pools = swimming_pool + swimming_pool; marathon + swimming_pool; // ERROR -- caught by compiler \end{lstlisting} @scalar@ is a dtype-static type, so all uses of it use a single struct definition, containing only a single @unsigned long@, and can share the same implementations of common routines like @?+?@ -- these implementations may even be separately compiled, unlike \CC template functions. However, the \CFA type-checker ensures that matching types are used by all calls to @?+?@, preventing nonsensical computations like adding the length of a marathon to the volume of an olympic pool. marathon + swimming_pool;                       $\C{// compilation ERROR}$ \end{lstlisting} @scalar@ is a dtype-static type, so all uses have a single structure definition, containing a single @unsigned long@, and can share the same implementations of common routines like @?+?@ -- these implementations may even be separately compiled, unlike \CC template functions. However, the \CFA type-checker ensures matching types are used by all calls to @?+?@, preventing nonsensical computations like adding a length to a volume. \section{Tuples} int ret = 0; while(N) { ret += va_arg(args, int);  // must specify type N--; ret += va_arg(args, int);  // must specify type N--; } va_end(args); forall(dtype T0, dtype T1, dtype T2 | sized(T0) | sized(T1) | sized(T2)) struct _tuple3 {  // generated before the first 3-tuple T0 field_0; T1 field_1; T2 field_2; T0 field_0; T1 field_1; T2 field_2; }; _tuple3_(int, double, int) y;