source: doc/theses/fangren_yu_MMath/intro.tex@ b28ce93

Last change on this file since b28ce93 was 8b3109b, checked in by Peter A. Buhr <pabuhr@…>, 4 months ago

proofread background chapter

  • Property mode set to 100644
File size: 64.0 KB
Line 
1\chapter{Introduction}
2
3This thesis is exploratory work I did to understand, fix, and extend the \CFA type-system, specifically, the \newterm{type-resolver} used to satisfy call-site assertions among overloaded variable and function names to allow polymorphic function calls.
4Assertions are the operations a function uses within its body to perform its computation.
5For example, a polymorphic function summing an array needs a size, zero, assignment, and plus for the array element-type, and a subscript operation for the array type.
6\begin{cfa}
7T sum( T a[$\,$], size_t size ) {
8 @T@ total = { @0@ }; $\C[1.75in]{// size, 0 for type T}$
9 for ( i; size )
10 total @+=@ a@[@i@]@; $\C{// + and subscript for T}\CRT$
11 return total;
12}
13\end{cfa}
14In certain cases, if the resolver fails to find an exact assertion match, it attempts to find a \emph{best} match using reasonable type conversions.
15Hence, \CFA follows the current trend of replacing nominal inheritance with traits composed of assertions for type matching.
16The over-arching goal in \CFA is to push the boundary on localized assertion matching, with advanced overloading resolution and type conversions that match programmer expectations in the C programming language.
17Together, the resulting type-system has a number of unique features making it different from other programming languages with expressive, static, type-systems.
18
19
20\section{Types}
21
22All computers have multiple types because computer architects optimize the hardware around a few basic types with well defined (mathematical) operations: boolean, integral, floating-point, and occasionally strings.
23A programming language and its compiler present ways to declare types that ultimately map into the ones provided by the underlying hardware.
24These language types are thrust upon programmers with their syntactic/semantic rules and restrictions.
25These rules are then used to transform a language expression to a unique hardware expression.
26Modern programming-languages allow user-defined types and generalize across multiple types using polymorphism.
27Type systems can be static, where each variable has a fixed type during execution and an expression's type is determined at compile time, or dynamic, where each variable can change type during execution and so an expression's type is reconstructed on each evaluation.
28Expressibility, generalization, and safety are all bound up in a language's type system, and hence, directly affect the capability, build time, and correctness of program development.
29
30
31\section{Overloading}
32\label{s:Overloading}
33
34\vspace*{-5pt}
35\begin{quote}
36There are only two hard things in Computer Science: cache invalidation and \emph{naming things}. --- Phil Karlton
37\end{quote}
38\vspace*{-5pt}
39Overloading allows programmers to use the most meaningful names without fear of name clashes within a program or from external sources, like include files.
40Experience from \CC and \CFA developers shows the type system can implicitly and correctly disambiguate the majority of overloaded names, \ie it is rare to get an incorrect selection or ambiguity, even among hundreds of overloaded (variables and) functions.
41In many cases, a programmer is unaware of name clashes, as they are silently resolved, simplifying the development process.
42
43Disambiguating among overloads is implemented by examining each call site and selecting the best matching overloaded function based on criteria like the types and number of arguments and the return context.
44Since the hardware does not support mixed-mode operands, such as @2 + 3.5@, the type system must disallow it or (safely) convert the operands to a common type.
45Like overloading, the majority of mixed-mode conversions are silently resolved, simplifying the development process.
46This approach matches with programmer intuition and expectation, regardless of any \emph{safety} issues resulting from converted values.
47Depending on the language, mix-mode conversions can be explicitly controlled using a cast.
48
49\newterm{Namespace pollution} refers to loading the global or other namespaces with many names, resulting in paranoia that the compiler could make wrong choices for overloaded names causing failure.
50This fear leads to coding styles where names are partitioned by language mechanisms and qualification is used to make names unique.
51This approach defeats the purpose of overloading and places an additional coding burden on both the developer and user.
52As well, many namespace systems provide a mechanism to open their scope returning to normal overloading, \ie no qualification.
53While namespace mechanisms are very important and provide a number of crucial program-development features, protection from overloading is overstated.
54Similarly, lexical nesting is another place where duplicate naming issues arise.
55For example, in object-oriented programming, class member names \newterm{shadow} names within members.
56Some programmers qualify all member names with @class::@ or @this->@ to make them unique from names defined in members.
57Even nested lexical blocks result in shadowing, \eg multiple nested loop-indices called @i@, silently changing the meaning of @i@ at lower scope levels.
58Again, coding styles exist requiring all variables in nested block to be unique to prevent name shadowing problems.
59Depending on the language, these possible ambiguities can be reported (as warnings or errors) and resolved explicitly using some form of qualification and/or cast.
60For example, if variables can be overloaded, shadowed variables of different type can produce ambiguities, indicating potential problems in lower scopes.
61
62Formally, overloading is defined by Strachey as one kind of \newterm{ad hoc polymorphism}:
63\vspace*{-5pt}
64\begin{quote}
65In ad hoc polymorphism there is no single systematic way of determining the type of the result from the type of the arguments.
66There may be several rules of limited extent which reduce the number of cases, but these are themselves ad hoc both in scope and content.
67All the ordinary arithmetic operators and functions come into this category.
68It seems, moreover, that the automatic insertion of transfer functions by the compiling system is limited to this.~\cite[p.~37]{Strachey00}
69\end{quote}
70\vspace*{-5pt}
71where a \newterm{transfer function} is an implicit conversion to help find a matching overload:
72\vspace*{-5pt}
73\begin{quote}
74The problem of dealing with polymorphic operators is complicated by the fact that the range of types sometimes overlap.
75Thus for example 3 may be an integer or a real and it may be necessary to change it from one type to the other.
76The functions which perform this operation are known as transfer functions and may either be used explicitly by the programmer, or, in some systems, inserted automatically by the compiling system.~\cite[p.~35]{Strachey00}
77\end{quote}
78\vspace*{-5pt}
79The differentiating characteristic between parametric polymorphism and overloading is often stated as: polymorphic functions use one algorithm to operate on arguments of many different types, whereas overloaded functions use a different algorithm for each type of argument.
80A similar differentiation is applicable for overloading and default parameters.
81\begin{cquote}
82\setlength{\tabcolsep}{10pt}
83\begin{tabular}{@{}lll@{}}
84\multicolumn{1}{c}{\textbf{different implementations}} & \multicolumn{1}{c}{\textbf{same implementation}} \\
85\begin{cfa}
86void foo( int );
87void foo( int, int );
88\end{cfa}
89&
90\begin{cfa}
91void foo( int, int = 5 ); // default value
92
93\end{cfa}
94\end{tabular}
95\end{cquote}
96However, this distinguishing characteristic is vague.
97For example, should the operation @abs@ be overloaded or polymorphic or both?
98\begin{cquote}
99\setlength{\tabcolsep}{10pt}
100\begin{tabular}{@{}lll@{}}
101\multicolumn{1}{c}{\textbf{overloading}} & \multicolumn{1}{c}{\textbf{polymorphic}} \\
102\begin{cfa}
103int abs( int );
104double abs( double );
105\end{cfa}
106&
107\begin{cfa}
108forall( T | { void ?{}( T &, zero_t ); int ?<?( T, T ); T -?( T ); } )
109T abs( T );
110\end{cfa}
111\end{tabular}
112\end{cquote}
113Here, there are performance advantages for having specializations and code-reuse advantages for the generalization.
114
115The Strachey definitions raise several questions.
116\begin{enumerate}[leftmargin=*]
117\item
118Is overloading polymorphism?
119
120\noindent
121In type theory, polymorphism allows an overloaded type name to represent multiple different types.
122For example, generic types overload the type name for a container type.
123\begin{cfa}
124@List@<int> li; @List@<double> ld; @List@<struct S> ls;
125\end{cfa}
126For subtyping, a derived type masquerades as a base type, where the base and derived names cannot be overloaded.
127Instead, the mechanism relies on structural typing among the types.
128In both cases, the polymorphic mechanisms apply in the type domain and the names are in the type namespace.
129In the following C example:
130\begin{cfa}
131struct S { int S; };
132struct S s;
133void S( struct S S ) { S.S = 1; };
134enum E { E };
135enum E e = E;
136\end{cfa}
137the overloaded names @S@ and @E@ are separated into the type and object domain, and C uses the type kinds @struct@ and @enum@ to disambiguate the names.
138In general, types are not overloaded because inferencing them is difficult to imagine in a statically typed programming language.
139\begin{cquote}
140\setlength{\tabcolsep}{26pt}
141\begin{tabular}{@{}ll@{}}
142\begin{cfa}
143struct S { int i; }; // 1
144struct S { int i, j }; // 2
145union S { int i; double d; }; // 3
146typedef int S; // 4
147\end{cfa}
148&
149\begin{cfa}
150S s;
151if ( ... ) s = (S){ 3 }; // 1
152else s = 3; // 4
153
154\end{cfa}
155\end{tabular}
156\end{cquote}
157On the other hand, in ad-hoc overloading of variables and/or functions, the names are in the object domain and each overloaded object name has an anonymous associated type.
158\begin{cfa}
159@double@ foo( @int@ ); @char@ foo( @void@ ); @int@ foo( @double, double@ );
160@double@ foo; @char@ foo; @int@ foo;
161\end{cfa}
162Here, the associated type cannot be extracted using @typeof@/\lstinline[language={[11]C++}]{decltype} for typing purposes, as @typeof( foo )@ is always ambiguous.
163To disambiguate the type of @foo@ requires additional information from the call-site or a cast.
164\begin{cfa}
165double d = foo( 3 ); char c = foo(); int i = foo( 3.5, 4.1 ); $\C{// exact match}$
166d = foo; c = foo; i = foo; $\C{// exact match}$
167i = ((double (*)( int ))foo)( 3.5 ); $\C{// no exact match, explicit cast => conversions}$
168\end{cfa}
169Hence, depending on your perspective, overloading may not be polymorphism, as no single overloaded entity represents multiple types.
170
171\item
172Does ad-hoc polymorphism have a single systematic way of determining the type of the result from the type of the arguments?
173
174\noindent
175For exact type matches in overloading, there is a systematic way of matching arguments to parameters, and a function denotes its return type rather than using type inferencing.
176This matching is just as robust as other polymorphic analysis.
177The ad-hoc aspect might be the implicit transfer functions (conversions) applied to arguments to create an exact parameter type-match, as there may be multiple conversions leading to different exact matches.
178Note, conversion issues apply to both non-overloaded and overloaded functions.
179Here, the selection of the conversion functions can be considered the \emph{opinion} of the language (type system), even if the technique used is based on sound rules, like maximizing conversion accuracy (non-lossy).
180The difference in opinion results when the language conversion rules differ from a programmer's expectations.
181However, without implicit conversions, programmers may have to write an exponential number of functions covering all possible exact-match cases among all reasonable types.
182\CFA's \emph{opinion} on conversions must match C's and then rely on programmers to understand the effects.
183That is, let the compiler do the heavy-lifting of selecting a \emph{best} set of conversions that maximize safety.
184Hence, removing implicit conversions from \CFA is not an option (as is true in most other programming languages), so it must do the best possible job to get it right.
185
186\item
187Why are there two forms of \emph{overloading} (general and parametric) in different programming languages?
188
189\noindent
190\newterm{General overloading} occurs when the type-system \emph{knows} a function's parameters and return types (or a variable's type for variable overloading).
191In functional programming-languages, there is always a return type.
192If a return type is specified, the compiler does not have to inference the function body.
193For example, the compiler has complete knowledge about builtin types and their overloaded arithmetic operators.
194In this context, there is a fixed set of overloads for a given name that are completely specified.
195Overload resolution then involves finding an exact match between a call and the overload prototypes based on argument type(s) and possibly return context.
196If an \emph{exact} match is not found, the call is either ill formed (ambiguous) or further attempts are made to find a \emph{best} match using transfer functions (conversions).
197As a consequence, no additional runtime information is needed per call, \ie the call is a direct transfer (branch) with possibly converted arguments.
198
199\noindent
200\newterm{Parametric overloading} occurs when the type-system does not know a function's parameter and return types.
201Types are inferred from constant types and/or constraint information that includes typing.
202Parametric overloading starts with a universal/generic type used to define parameters and returns.
203\begin{cfa}
204forall( T ) T identity( T t ) { return t; }
205int i = identify( 3 );
206double d;
207double e = identity( d );
208\end{cfa}
209Here, the type system is substituting the argument type for @T@, and ensuring the return type also matches.
210This mechanism can be extended to overloaded parametric functions with multiple type parameters.
211\begin{cfa}
212forall( T ) [T, T] identity( T t1, T t2 ) { return [t1, t2 ]; } // return tuple
213int j;
214[i, j] = identity( 3, 4 );
215forall( T, U ) [T, U] identity( T t1, U t2 ) { return [t1, t2 ]; }
216[i, d] = identity( i, d );
217\end{cfa}
218Here, the type system is using techniques from general overloading, number and argument types, to disambiguate the overloaded parametric functions.
219However, this exercise is moot because the types are so universal they have no practical operations within the function, modulo any general operations carried by every type, like copy or print.
220This kind of polymorphism is restricted to moving type instances as abstract entities;
221if type erasure is used, there is no way to recover the original type to perform useful actions on its value(s).
222Hence, parametric overloading requires additional information about the universal types to make them useful.
223
224This additional information often comes as a set of operations that must be supply for a type, \eg \CFA/Rust/Go have traits, \CC template has concepts, Haskell has type-classes.
225These operations can then be used in the body of the function to manipulate the type's value.
226Here, a type binding to @T@ must have available a @++@ operation with the specified signature.
227\begin{cfa}
228forall( T | @T ?++( T, T )@ ) // trait
229T inc( T t ) { return t@++@; } // change type value
230int i = 3
231i = inc( i )
232double d = inc( 3.5 );
233\end{cfa}
234Given a qualifying trait, are its elements inferred or declared?
235In the example, the type system infers @int@ for @T@, infers it needs an appropriately typed @++@ operator, and finds it in the enclosing environment, possibly in the language's prelude defining basic types and their operations.
236This implicit inferencing is expensive if matched with implicit conversions when there is no exact match.
237Alternatively, types opt-in to traits via declarations.
238\begin{cfa}
239trait postinc { T ?@++@( T, T ) }
240type int provide postinc;
241\end{cfa}
242In this approach, after inferring @int@ for @T@, @int@ is examined for any necessary traits required in the function, so there is no searching.
243Once all traits are satisfied, the compiler can inline them, pass functions pointers directly, or pass a virtual table composing the trait.
244\end{enumerate}
245The following sections illustrate how overloading is provided in \CFA.
246
247\begin{comment}
248\begin{haskell}
249f( int, int ); f( int, float ); -- return types to be determined
250g( int, int ); g( float, int );
251let x = curry f( 3, _ ); -- which f
252let y = curry g( _ , 3 ); -- which g
253\end{haskell}
254For the currying to succeed, there cannot be overloaded function names resulting in ambiguities.
255To allow currying to succeed requires an implicit disambiguating mechanism, \ie a kind of transfer function.
256A type class~\cite{typeclass} is a mechanism to convert overloading into parametric polymorphism.
257Parametric polymorphism has enough information to disambiguate the overloaded names because it removes the type inferencing.
258\begin{cfa}
259forall( T | T has + $and$ - ) T f$\(_1\)$( T );
260forall( T | T has * $and$ - ) T f$\(_2\)$( T );
261x = f$\(_1\)$( x ); // if x has + and - but not *
262y = f$\(_2\)$( y ); // if y has * and - but not +
263\end{cfa}
264Here, the types of @x@ and @y@ are combined in the type-class contraints to provide secondary infomration for disambiguation.
265This approach handles many overloading cases because the contraints overlap completely or are disjoint
266
267A type class (trait) generically abstracts the set of the operations used in a function's implementation.
268A type-class instance binds a specific type to the generic operations to form concrete instances, giving a name type-class.
269Then Qualified types concisely express the operations required to convert an overloaded
270The name type-class is used as a transfer function to convert an overloaded function into a polymorphic function that is uniquely qualified with the name type-class.
271\begin{cfa}
272void foo_int_trait( special int trait for operations in this foo );
273void foo_int_int_trait( special (int, int) trait for operations in this foo );
274\end{cfa}
275
276In this case, the compiler implicitly changes the overloaded function to a parametrically polymorphic one.
277Hence, the programmer does specify any additional information for the overloading to work.
278Explicit overloading occurs when the compiler has to be told what operations are associated with a type programmer separately defines the associate type and subsequently associates the type with overloaded name.
279\end{comment}
280
281\begin{comment}
282Date: Mon, 24 Feb 2025 11:26:12 -0500
283Subject: Re: overloading
284To: "Peter A. Buhr" <pabuhr@uwaterloo.ca>
285CC: <f37yu@uwaterloo.ca>, <ajbeach@uwaterloo.ca>, <mlbrooks@uwaterloo.ca>,
286 <alvin.zhang@uwaterloo.ca>, <lseo@plg.uwaterloo.ca>,
287 <j82liang@uwaterloo.ca>
288From: Gregor Richards <gregor.richards@uwaterloo.ca>
289
290Yes.
291
292With valediction,
293 - Gregor Richards
294
295On 2/24/25 11:22, Peter A. Buhr wrote:
296> Gregor Richards <gregor.richards@uwaterloo.ca> writes:
297> In Haskell, `+` works for both because of typeclasses (inclusion
298> polymorphism), and so is also not an unresolved type.
299>
300> I'm making this up. The Haskell type-class is a trait, like an interface or
301> abstract class, and its usage/declaration/binding creates a specific trait
302> instance for bound types, which is a vtable filled with the typed functions
303> instantiated/located for the trait. The vtables are present at runtime and
304> passed implicitly to ad-hoc polymorphic functions allowing differentiate of
305> overloaded functions based on the number of traits and their specialization.
306> (Major geek talk, YA! 8-)
307>
308> On 2/21/25 23:04, Fangren Yu wrote:
309> > In a statically typed language I would rather have definitions like
310> > double x = x+x be ambiguous than "an unresolved type" as the latter
311> > sounds like a weaker version of a generic type, and being able to make
312> > something generic without explicitly saying so is probably not a good
313> > idea. Giving the unspecified parameter type an arbitrary preference is
314> > the second best option IMO (does ML give you a warning on such not
315> > fully inferred types?)
316> > ------------------------------------------------------------------------
317> > *From:* Gregor Richards <gregor.richards@uwaterloo.ca>
318> > *Sent:* Wednesday, February 19, 2025 9:55:23 PM
319> > *To:* Peter Buhr <pabuhr@uwaterloo.ca>
320> > *Cc:* Andrew James Beach <ajbeach@uwaterloo.ca>; Michael Leslie Brooks
321> > <mlbrooks@uwaterloo.ca>; Fangren Yu <f37yu@uwaterloo.ca>;
322> > j82liang@uwaterloo.ca <j82liang@uwaterloo.ca>; Alvin Zhang
323> > <alvin.zhang@uwaterloo.ca>; lseo@plg.uwaterloo.ca <lseo@plg.uwaterloo.ca>
324> > *Subject:* Re: overloading
325> > Jives with what I was saying, albeit not exactly the same; it's a result
326> > of the same problem.
327> >
328> > 'doubles' refers to an unresolved 'double', and the latter can't be
329> > resolved without the former, so you can't compile 'double' unless you
330> > know what its arguments are. The solutions are:
331> >
332> > * Typeclasses make it possible by compiling with a handle. When you
333> > call a function that takes a typeclass value as an argument, it takes an
334> > extra, hidden argument internally which is the typeclass handle. That
335> > handle tells the callee how to use the typeclass functions with this
336> > particular value. And, of course, you hope that some aggressive inlining
337> > gets rid of the dynamic dispatch :). But, no per se overloading
338> > supported, only inclusion polymorphism.
339> >
340> > * If you do whole-world compilation, then you just compile what you
341> > particularly need in context. If you call 'doubles' with a
342> > float,int,int, then you compile that version. But, currying is unsolved.
343> >
344> > * If you do C++-style templating, this is a less severe problem, as
345> > you compile it with the use of 'doubles', not with the definition. But,
346> > either no currying, or you have to specify the extra types explicitly so
347> > it knows what to curry, so no inference.
348> >
349> > * If you do Java-style generics, ... just kidding.
350> >
351> > In a language like Haskell or OCaml, if you want to compile this
352> > modularly and have the code with the implementation, then naively it
353> > would have to make eight implementations. But, even that is only true if
354> > (x, y, z) is a single tuple argument. Currying is still the killer. If
355> > you call `doubles 3`, the return is supposed to be some x -> y -> (int, x,
356> > y), where 'x' and 'y' are "some type on which I can call a 'double'
357> > function, but I don't know which double function yet because I don't
358> > know what type". Even *writing* that type is hard enough, but having
359> > values of that type float around at runtime? Yikes.
360> >
361> > To put it a different way: In C++ (and presumably CFA?), you can
362> > overload all you want to, but you can't get a function pointer to an
363> > unresolved overload. The function pointer is to a *particular* overload,
364> > not the set of possible overloads. Well, in a functional language,
365> > function pointers are the lifeblood of the language.
366> >
367> > With valediction,
368> > - Gregor Richards
369> >
370> > On 2/19/25 21:25, Peter A. Buhr wrote:
371> > > In the "Type Classes" chapter I sent out, the author says the
372> > following. Does it
373> > > jive with what you are saying about currying? BTW, I do not know who
374> > wrote the
375> > > book chapter.
376> > >
377> > >
378> > ==========================================================================
379> > >
380> > > Suppose we have a language that overloads addition + and
381> > multiplication *,
382> > > providing versions that work over values of type Int and type Float.
383> > Now,
384> > > consider the double function, written in terms of the overloaded
385> > addition
386> > > operation:
387> > >
388> > > double x = x + x
389> > >
390> > > What does this definition mean? A naive interpretation would be to
391> > say that
392> > > double is also overloaded, defining one function of type Int -> Int
393> > -> Int and a
394> > > second of type Float -> Float -> Float. All seems fine, until we
395> > consider the
396> > > function
397> > >
398> > > doubles (x,y,z) = (double x, double y, double z)
399> > >
400> > > Under the proposed scheme, this definition would give rise to eight
401> > different
402> > > versions! This approach has not been widely used because of the
403> > exponential
404> > > growth in the number of versions.
405> > >
406> > > To avoid this blow-up, language designers have sometimes restricted the
407> > > definition of overloaded functions. In this approach, which was
408> > adopted in
409> > > Standard ML, basic operations can be overloaded, but not functions
410> > defined in
411> > > terms of them. Instead, the language design specifies one of the
412> > possible
413> > > versions as the meaning of the function. For example, Standard ML give
414> > > preference to the type int over real, so the type (and
415> > implementation) of the
416> > > function double would be int -> int. If the programmer wanted to
417> > define a double
418> > > function over floating point numbers, she would have to explicitly
419> > write the
420> > > type of the function in its definition and give the function a name
421> > distinct
422> > > from the double function on integers. This approach is not particularly
423> > > satisfying, because it violates a general principle of language
424> > design: giving
425> > > the compiler the ability to define features that programmers cannot.
426>
427> [2:text/html Show Save:noname (10kB)]
428\end{comment}
429
430
431\subsection{Operator Overloading}
432
433Many programming languages provide general overloading of the arithmetic operators~\cite{OperOverloading} across the basic computational types using the number and type of parameters and returns.
434However, in some languages, arithmetic operators may not be first class, and hence, cannot be overloaded.
435Like \CC, \CFA allows general operator overloading for user-defined types.
436The \CFA syntax for operator names uses the @'?'@ character to denote a parameter, \eg left and right unary operators: @?++@ and @++?@, and binary operators @?+?@ and @?<=?@.
437Here, a user-defined type is extended with an addition operation with the same syntax as a builtin type.
438\begin{cfa}
439struct S { int i, j };
440S @?+?@( S op1, S op2 ) { return (S){ op1.i + op2.i, op1.j + op2.j }; }
441S s1, s2;
442s1 = s1 @+@ s2; $\C[1.75in]{// infix call}$
443s1 = @?+?@( s1, s2 ); $\C{// direct call}\CRT$
444\end{cfa}
445\CFA excludes overloading the comma operator, short-circuit logical operators \lstinline{&&} and \lstinline{||}, and ternary conditional operator \lstinline{?} (\eg @max = x > y ? x : y@), all of which are short-hand control structures rather than operators.
446\CC \emph{does} overload the comma and short-circuit logical operators, where overloading the comma operator is esoteric and rarely used, and the short-circuit operators do not exhibit short-circuit semantics, both of which are confusing and/or inconsistent.
447
448
449\subsection{Function Overloading}
450
451Many programming languages provide general overloading for functions~\cite{FuncOverloading}, as long as their prototypes differ in the number and type of parameters.
452A few programming languages also use the return type for selecting overloaded functions \see{below}.
453\begin{cfa}
454void f( void ); $\C[2in]{// (1): no parameter}$
455void f( char ); $\C{// (2): overloaded on the number and parameter type}$
456void f( int, int ); $\C{// (3): overloaded on the number and parameter type}$
457f( 'A' ); $\C{// select (2)}\CRT$
458\end{cfa}
459The type system examines each call site and first looks for an exact match and then a best match using conversions.
460
461Ada, Scala, and \CFA type-systems also use the return type in resolving a call, to pinpoint the best overloaded name.
462Essentially, the return types are \emph{reversed curried} into output parameters of the function.
463For example, in many programming languages with overloading, the following functions are ambiguous without using the return type.
464\begin{cfa}
465int f( int ); $\C[2in]{// (1); overloaded on return type and parameter}$
466double f( int ); $\C{// (2); overloaded on return type and parameter}$
467int i = f( 3 ); $\C{// select (1)}$
468double d = f( 3 ); $\C{// select (2)}\CRT$
469\end{cfa}
470Alternatively, if the type system uses the return type, there is an exact match for each call, which again matches with programmer intuition and expectation.
471This capability can be taken to the extreme, where the only differentiating factor is the return type.
472\begin{cfa}
473int random( void ); $\C[2in]{// (1); overloaded on return type}$
474double random( void ); $\C{// (2); overloaded on return type}$
475int i = random(); $\C{// select (1)}$
476double d = random(); $\C{// select (2)}\CRT$
477\end{cfa}
478Again, there is an exact match for each call.
479As for operator overloading, if there is no exact match, a set of minimal implicit conversions can be added to find a best match.
480\begin{cfa}
481short int = random(); $\C[2in]{// select (1), unsafe}$
482long double = random(); $\C{// select (2), safe}\CRT$
483\end{cfa}
484
485
486\subsection{Variable Overloading}
487
488Unlike most programming languages, \CFA has variable overloading within a scope, along with shadow overloading in nested scopes.
489Shadow overloading is also possible for functions, in languages supporting nested-function declarations, \eg \CC named, nested, lambda functions.
490\begin{cfa}
491void foo( double d );
492int v; $\C[2in]{// (1)}$
493double v; $\C{// (2) variable overloading}$
494foo( v ); $\C{// select (2)}$
495{
496 int v; $\C{// (3) shadow overloading}$
497 double v; $\C{// (4) and variable overloading}$
498 foo( v ); $\C{// select (4)}\CRT$
499}
500\end{cfa}
501It is interesting that shadowing \see{namespace pollution in \VRef{s:Overloading}} is considered a normal programming-language feature with only slight software-engineering problems.
502However, variable overloading within a scope is often considered dangerous, without any evidence to corroborate this claim.
503In contrast, function overloading in \CC occurs silently within the global scope from @#include@ files all the time without problems.
504
505In \CFA, the type system simply treats an overloaded variable as an overloaded function returning a value with no parameters.
506Hence, no effort is required to support this feature as it is available for differentiating among overloaded functions with no parameters.
507\begin{cfa}
508int MAX = 2147483647; $\C[2in]{// (1); overloaded on return type}$
509long int MAX = ...; $\C{// (2); overloaded on return type}$
510double MAX = ...; $\C{// (3); overloaded on return type}$
511int i = MAX; $\C{// select (1)}$
512long int i = MAX; $\C{// select (2)}$
513double d = MAX; $\C{// select (3)}\CRT$
514\end{cfa}
515Hence, the name @MAX@ can replace all the C type-specific names, \eg @INT_MAX@, @LONG_MAX@, @DBL_MAX@, \etc.
516The result is a significant reduction in names to access common constants.
517
518
519\subsection{Constant Overloading}
520
521\CFA is unique in providing restricted constant overloading for the values @0@ and @1@, which have special status in C.
522For example, the value @0@ is both an integer and a pointer literal, so its meaning depends on context.
523In addition, several operations are defined in terms of values @0@ and @1@.
524For example, @if@ and iteration statements in C compare the condition with @0@, and the increment and decrement operators are semantically equivalent to adding or subtracting the value @1@.
525\begin{cfa}
526if ( x ) ++x; => if ( x @!= 0@ ) x @+= 1@;
527for ( ; x; --x ) => for ( ; x @!= 0@; x @-= 1@ )
528\end{cfa}
529To generalize this feature, both constants are given types @zero_t@ and @one_t@ in \CFA, which allows overloading various operations for new types that seamlessly work within the special @0@ and @1@ contexts.
530The types @zero_t@ and @one_t@ have special builtin implicit conversions to the various integral types, and a conversion to pointer types for @0@, which allows standard C code involving @0@ and @1@ to work.
531\begin{cfa}
532struct S { int i, j; };
533void ?{}( S & s, zero_t ) { s.[i,j] = 0; } $\C{// constant constructors}$
534void ?{}( S & s, one_t ) { s.[i,j] = 1; }
535S ?=?( S & dst, zero_t ) { dst.[i,j] = 0; return dst; } $\C{// constant assignments}$
536S ?=?( S & dst, one_t ) { dst.[i,j] = 1; return dst; }
537S ?+=?( S & s, one_t ) { s.[i,j] += 1; return s; } $\C{// increment/decrement each field}$
538S ?-=?( S & s, one_t ) { s.[i,j] -= 1; return s; }
539int ?!=?( S s, zero_t ) { return s.i != 0 && s.j != 0; } $\C{// constant comparison}$
540S s = @0@; $\C{// initialization}$
541s = @0@; $\C{// assignments}$
542s = @1@;
543if ( @s@ ) @++s@; $\C{// unary ++/-\,- come implicitly from +=/-=}$
544\end{cfa}
545Here, type @S@ is first-class with respect to the basic types, working with all existing implicit C mechanisms.
546
547
548\section{Type Inferencing}
549\label{s:IntoTypeInferencing}
550
551Every variable has a type, but association between them can occur in different ways:
552at the point where the variable comes into existence (declaration) and/or on each assignment to the variable.
553\begin{cfa}
554double x; $\C{// type only}$
555float y = 3.1D; $\C{// type and initialization}$
556auto z = y; $\C{// initialization only}$
557z = "abc"; $\C{// assignment}$
558\end{cfa}
559For type-only, the programmer specifies the initial type, which remains fixed for the variable's lifetime in statically typed languages.
560For type-and-initialization, the specified and initialization types may not agree requiring an implicit/explicit conversion.
561For initialization-only, the compiler may select the type by melding programmer and context information.
562When the compiler participates in type selection, it is called \newterm{type inferencing}.
563Note, type inferencing is different from type conversion: type inferencing \emph{discovers} a variable's type before setting its value, whereas conversion has two typed variables and performs a (possibly lossy) value conversion from one type to the other.
564Finally, for assignment, the current variable and expression types may not agree.
565Discovering a variable or function type is complex and has limitations.
566The following covers these issues, and why this scheme is not amenable with the \CFA type system.
567
568One of the first and most powerful type-inferencing systems is Hindley--Milner~\cite{Damas82}.
569Here, the type resolver starts with the types of the program constants used for initialization and these constant types flow throughout the program, setting all variable and expression types.
570\begin{cfa}
571auto f() {
572 x = 1; y = 3.5; $\C{// set types from constants}$
573 x = // expression involving x, y and other local initialized variables
574 y = // expression involving x, y and other local initialized variables
575 return x, y;
576}
577auto w = f(); $\C{// typing flows outwards}$
578
579void f( auto x, auto y ) {
580 x = // expression involving x, y and other local initialized variables
581 y = // expression involving x, y and other local initialized variables
582}
583s = 1; t = 3.5; $\C{// set types from constants}$
584f( s, t ); $\C{// typing flows inwards}$
585\end{cfa}
586In both overloads of @f@, the type system works from the constant initializations inwards and/or outwards to determine the types of all variables and functions.
587Like template meta-programming, there can be a new function generated for the second @f@ depending on the types of the arguments, assuming these types are meaningful in the body of @f@.
588Inferring type constraints, by analysing the body of @f@ is possible, and these constraints must be satisfied at each call site by the argument types;
589in this case, parametric polymorphism can allow separate compilation.
590In languages with type inferencing, there is often limited overloading to reduce the search space, which introduces the naming problem.
591Note, return-type inferencing goes in the opposite direction to Hindley--Milner: knowing the type of the result and flowing back through an expression to help select the best possible overloads, and possibly converting the constants for a best match.
592
593There are multiple ways to indirectly specify a variable's type, \eg from a prior variable or expression.
594\begin{cquote}
595\setlength{\tabcolsep}{10pt}
596\begin{tabular}{@{}lll@{}}
597\multicolumn{1}{c}{\textbf{gcc / \CFA}} & \multicolumn{1}{c}{\textbf{\CC}} \\
598\begin{cfa}
599#define expr 3.0 * i
600typeof(expr) x = expr;
601int y;
602typeof(y) z = y;
603\end{cfa}
604&
605\begin{cfa}
606
607auto x = 3.0 * i;
608int y;
609auto z = y;
610\end{cfa}
611&
612\begin{cfa}
613
614// use type of initialization expression
615
616// use type of initialization expression
617\end{cfa}
618\end{tabular}
619\end{cquote}
620Here, @type(expr)@ computes the same type as @auto@ righ-hand expression.
621The advantages are:
622\begin{itemize}[topsep=0pt]
623\item
624Not determining or writing long generic types, \eg, given deeply nested generic types.
625\begin{cfa}
626typedef T1(int).T2(float).T3(char).T @ST@; $\C{// \CFA nested type declaration}$
627@ST@ x, y, x;
628\end{cfa}
629This issue is exaggerated with \CC templates, where type names are 100s of characters long, resulting in unreadable error messages.
630\item
631Ensuring the type of secondary variables match a primary variable.
632\begin{cfa}
633int x; $\C{// primary variable}$
634typeof(x) y, z, w; $\C{// secondary variables match x's type}$
635\end{cfa}
636If the type of @x@ changes, the type of the secondary variables correspondingly updates.
637There can be strong software-engineering reasons for binding the types of these variables.
638\end{itemize}
639Note, the use of @typeof@ is more restrictive, and possibly safer, than general type-inferencing.
640\begin{cquote}
641\setlength{\tabcolsep}{20pt}
642\begin{tabular}{@{}ll@{}}
643\begin{cfa}
644int x;
645type(x) y = ... // complex expression
646type(x) z = ... // complex expression
647\end{cfa}
648&
649\begin{cfa}
650int x;
651auto y = ... // complex expression
652auto z = ... // complex expression
653\end{cfa}
654\end{tabular}
655\end{cquote}
656On the left, the types of @y@ and @z@ are fixed (branded), whereas on the right, the types of @y@ and @z@ can fluctuate.
657
658
659\subsection{Type-Inferencing Issues}
660
661Each kind of type-inferencing system has its own set of issues that affect the programmer in the form of convenience, restrictions, or confusions.
662
663A convenience is having the compiler use its overarching program knowledge to select the best type for each variable based on some notion of \emph{best}, which simplifies the programming experience.
664
665A restriction is the conundrum in type inferencing of when to \emph{brand} a type.
666That is, when is the type of the variable/function more important than the type of its initialization expression(s).
667For example, if a change is made in an initialization expression, it can cascade type changes producing many other changes and/or errors.
668At some point, a variable's type needs to remain constant and the initializing expression needs to be modified or be in error when it changes.
669Often type-inferencing systems allow restricting (\newterm{branding}) a variable or function type, so the compiler can report a mismatch with the constant initialization.
670\begin{cfa}
671void f( @int@ x, @int@ y ) { // brand function prototype
672 x = // expression involving x, y and other local initialized variables
673 y = // expression involving x, y and other local initialized variables
674}
675s = 1; t = 3.5;
676f( s, @t@ ); // type mismatch
677\end{cfa}
678In Haskell, it is common for programmers to brand (type) function parameters.
679
680A confusion is blocks of code where all declarations are @auto@, as is now common in \CC.
681As a result, understanding and changing the code becomes almost impossible.
682Types provide important clues as to the behaviour of the code, and correspondingly to correctly change or add new code.
683In these cases, a programmer is forced to re-engineer types, which is fragile, or rely on an IDE that can re-engineer types for them.
684For example, given:
685\begin{cfa}
686auto x = @...@
687\end{cfa}
688and the need to write a function to compute using @x@
689\begin{cfa}
690void rtn( @type of x@ parm );
691rtn( x );
692\end{cfa}
693A programmer must re-engineer the type of @x@'s initialization expression, reconstructing the possibly long generic type-name.
694In this situation, having the type name or its short alias is essential.
695
696\CFA's type system tries to prevent type-resolution mistakes by relying heavily on the type of the left-hand side of assignment to pinpoint correct types within an expression.
697Type inferencing defeats this goal because there is no left-hand type.
698Fundamentally, type inferencing tries to remove explicit typing from programming.
699However, writing down types is an important aspect of good programming, as it provides a check of the programmer's expected type and the actual type.
700Thinking carefully about types is similar to thinking carefully about date structures, often resulting in better performance and safety.
701Similarly, thinking carefully about storage management in unmanaged languages is an important aspect of good programming, versus implicit storage management (garbage collection) in managed language.\footnote{
702There are full-time Java consultants, who are hired to find memory-management problems in large Java programs, \eg Monika Beckworth.}
703Given @typedef@ and @typeof@, and the strong desire to use the left-hand type in resolution, no attempt has been made in \CFA to support implicit type-inferencing.
704Should a significant need arise, this decision can be revisited.
705
706
707\section{Polymorphism}
708
709\CFA provides polymorphic functions and types, where a polymorphic function can constrain types using assertions based on traits.
710
711
712\subsection{Polymorphic Function}
713\label{s:PolymorphicFunction}
714
715The signature feature of the \CFA type-system is parametric-polymorphic functions~\cite{forceone:impl,Cormack90,Duggan96}, generalized using a @forall@ clause (giving the language its name).
716\begin{cfa}
717@forall( T )@ T identity( T val ) { return val; }
718int forty_two = identity( 42 ); $\C{// T is bound to int, forty\_two == 42}$
719\end{cfa}
720This @identity@ function can be applied to an \newterm{object type}, \ie a type with a known size and alignment, which is sufficient to stack allocate, default or copy initialize, assign, and delete.
721The \CFA implementation passes the size and alignment for each type parameter, as well as auto-generated default and copy constructors, assignment operator, and destructor.
722For an incomplete \newterm{data type}, \eg pointer/reference types, this information is not needed.
723\begin{cfa}
724forall( T * ) T * identity( T * val ) { return val; }
725int i, * ip = identity( &i );
726\end{cfa}
727Unlike \CC template functions, \CFA polymorphic functions are compatible with \emph{separate compilation}, preventing compilation and code bloat.
728
729To constrain polymorphic types, \CFA uses \newterm{type assertions}~\cite[pp.~37-44]{Alphard} to provide further type information, where type assertions may be variable or function declarations that depend on a polymorphic type variable.
730Here, the function @twice@ works for any type @T@ with a matching addition operator.
731\begin{cfa}
732forall( T @| { T ?+?(T, T); }@ ) T twice( T x ) { return x @+@ x; }
733int val = twice( twice( 3 ) ); $\C{// val == 12}$
734\end{cfa}
735The closest approximation to parametric polymorphism and assertions in C is type-unsafe (@void *@) functions, like @qsort@ for sorting an array of unknown values.
736\begin{cfa}
737void qsort( void * base, size_t nmemb, size_t size, int (*cmp)( const void *, const void * ) );
738\end{cfa}
739Here, the polymorphism is type-erasure, and the parametric assertion is the comparison function, which is explicitly passed.
740\begin{cfa}
741enum { N = 5 };
742double val[N] = { 5.1, 4.1, 3.1, 2.1, 1.1 };
743int cmp( const void * v1, const void * v2 ) { $\C{// compare two doubles}$
744 return *(double *)v1 < *(double *)v2 ? -1 : *(double *)v2 < *(double *)v1 ? 1 : 0;
745}
746qsort( val, N, sizeof( double ), cmp );
747\end{cfa}
748The equivalent type-safe version in \CFA is a wrapper over the C version.
749\begin{cfa}
750forall( ET | { int @?<?@( ET, ET ); } ) $\C{// type must have < operator}$
751void qsort( ET * vals, size_t dim ) {
752 int cmp( const void * t1, const void * t2 ) { $\C{// nested function}$
753 return *(ET *)t1 @<@ *(ET *)t2 ? -1 : *(ET *)t2 @<@ *(ET *)t1 ? 1 : 0;
754 }
755 qsort( vals, dim, sizeof(ET), cmp ); $\C{// call C version}$
756}
757qsort( val, N ); $\C{// deduct type double, and pass builtin < for double}$
758\end{cfa}
759The nested function @cmp@ is implicitly built and provides the interface from typed \CFA to untyped (@void *@) C.
760Providing a hidden @cmp@ function in \CC is awkward as lambdas do not use C calling conventions and template declarations cannot appear in block scope.
761% In addition, an alternate kind of return is made available: position versus pointer to found element.
762% \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 template @bsearch@.
763Call-site inferencing and nested functions provide a localized form of inheritance.
764For example, the \CFA @qsort@ can be made to sort in descending order by locally changing the behaviour of @<@.
765\begin{cfa}
766{
767 int ?<?( double x, double y ) { return x @>@ y; } $\C{// locally override behaviour}$
768 qsort( vals, 10 ); $\C{// descending sort}$
769}
770\end{cfa}
771The local version of @?<?@ overrides the built-in @?<?@ so it is passed to @qsort@.
772The local version performs @?>?@, making @qsort@ sort in descending order.
773Hence, any number of assertion functions can be overridden locally to maximize the reuse of existing functions and types, without the construction of a named inheritance hierarchy.
774A final example is a type-safe wrapper for C @malloc@, where the return type supplies the type/size of the allocation, which is impossible in most type systems.
775\begin{cfa}
776static inline forall( T & | sized(T) )
777T * malloc( void ) {
778 if ( _Alignof(T) <= __BIGGEST_ALIGNMENT__ ) return (T *)malloc( sizeof(T) ); // C allocation
779 else return (T *)memalign( _Alignof(T), sizeof(T) );
780}
781// select type and size from left-hand side
782int * ip = malloc(); double * dp = malloc(); [[aligned(64)]] struct S {...} * sp = malloc();
783\end{cfa}
784The @sized@ assertion passes size and alignment as a data object has no implicit assertions.
785Both assertions are used in @malloc@ via @sizeof@ and @_Alignof@.
786In practice, this polymorphic @malloc@ is unwrapped by the C compiler and the @if@ statement is elided producing a type-safe call to @malloc@ or @memalign@.
787
788This mechanism is used to construct type-safe wrapper-libraries condensing hundreds of existing C functions into tens of \CFA overloaded functions.
789Here, existing C legacy code is leveraged as much as possible;
790other programming languages must build supporting libraries from scratch, even in \CC.
791
792
793\subsection{Traits}
794\label{s:Traits}
795
796\CFA provides \newterm{traits} to name a group of type assertions, where the trait name allows specifying the same set of assertions in multiple locations, preventing repetition mistakes at each function declaration.
797\begin{cquote}
798\begin{tabular}{@{}l|@{\hspace{10pt}}l@{}}
799\begin{cfa}
800forall( T ) trait @sumable@ {
801 void @?{}@( T &, zero_t ); // 0 literal constructor
802 T @?+=?@( T &, T ); // assortment of additions
803 T ?+?( T, T );
804 T ++?( T & );
805 T ?++( T & );
806};
807\end{cfa}
808&
809\begin{cfa}
810forall( T @| sumable( T )@ ) // use trait
811T sum( T a[$\,$], size_t size ) {
812 @T@ total = 0; // initialize by 0 constructor
813 for ( i; size )
814 total @+=@ a[i]; // select appropriate +
815 return total;
816}
817\end{cfa}
818\end{tabular}
819\end{cquote}
820Traits are implemented by flattening them at use points, as if written in full by the programmer.
821Flattening often results in overlapping assertions, \eg operator @+@.
822Hence, trait names play no part in type equivalence.
823In the example, type @T@ is an object type, and hence, has the implicit internal trait @otype@.
824\begin{cfa}
825trait otype( T & | sized(T) ) {
826 void ?{}( T & ); $\C{// default constructor}$
827 void ?{}( T &, T ); $\C{// copy constructor}$
828 void ?=?( T &, T ); $\C{// assignment operator}$
829 void ^?{}( T & ); $\C{// destructor}$
830};
831\end{cfa}
832These implicit functions are used by the @sumable@ operator @?+=?@ for the right side of @?+=?@ and return.
833
834If the array type is not a builtin type, an extra type parameter and assertions are required, like subscripting.
835This case is generalized in polymorphic container-types, such as a list with @insert@ and @remove@ operations, and an element type with copy and assignment.
836
837
838\subsection{Generic Types}
839
840A significant shortcoming of standard C is the lack of reusable type-safe abstractions for generic data structures and algorithms.
841Broadly speaking, there are three approaches to implement abstract data structures in C.
842\begin{enumerate}[leftmargin=*]
843\item
844Write bespoke data structures for each context.
845While this approach is flexible and supports integration with the C type checker and tooling, it is tedious and error prone, especially for more complex data structures.
846
847\item
848Use @void *@-based polymorphism, \eg the C standard library functions @bsearch@ and @qsort@, which allow for the reuse of code with common functionality.
849However, this approach eliminates the type checker's ability to ensure argument types are properly matched, often requiring a number of extra function parameters, pointer indirection, and dynamic allocation that is otherwise unnecessary.
850
851\item
852Use an internal macro capability, like \CC @templates@, to generate code that is both generic and type checked, but errors may be difficult to interpret.
853Furthermore, writing complex template macros is difficult and complex.
854
855\item
856Use an external macro capability, like M4~\cite{M4}, to generate code that is generic code, but errors may be difficult to interpret.
857Like internal macros, writing and using external macros is equally difficult and complex.
858\end{enumerate}
859
860\CC, Java, and other languages use \newterm{generic types} to produce type-safe abstract data-types.
861\CFA generic types integrate efficiently and naturally with the existing polymorphic functions, while retaining backward compatibility with C and providing separate compilation.
862For concrete parameters, the generic-type definition can be inlined, like \CC templates, if its definition appears in a header file (\eg @static inline@).
863
864A 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.
865\begin{cquote}
866\begin{tabular}{@{}l|@{\hspace{10pt}}l@{}}
867\begin{cfa}
868@forall( F, S )@ struct pair {
869 F first; S second;
870};
871@forall( F, S )@ // object
872S second( pair( F, S ) p ) { return p.second; }
873@forall( F *, S * )@ // sized
874S * second( pair( F *, S * ) p ) { return p.second; }
875\end{cfa}
876&
877\begin{cfa}
878pair( double, int ) dpr = { 3.5, 42 };
879int i = second( dpr );
880pair( void *, int * ) vipr = { 0p, &i };
881int * ip = second( vipr );
882double d = 1.0;
883pair( int *, double * ) idpr = { &i, &d };
884double * dp = second( idpr );
885\end{cfa}
886\end{tabular}
887\end{cquote}
888\label{s:GenericImplementation}
889\CFA generic types are \newterm{fixed} or \newterm{dynamic} sized.
890Fixed-size types have a fixed memory layout regardless of type parameters, whereas dynamic types vary in memory layout depending on the type parameters.
891For example, the type variable @T *@ is fixed size and is represented by @void *@ in code generation;
892whereas, the type variable @T@ is dynamic and set at the point of instantiation.
893The difference between fixed and dynamic is the complexity and cost of field access.
894For fixed, field offsets are computed (known) at compile time and embedded as displacements in instructions.
895For dynamic, field offsets are compile-time computed at the call site, stored in an array of offset values, passed as a polymorphic parameter, and added to the structure address for each field dereference within a polymorphic function.
896See~\cite[\S~3.2]{Moss19} for complete implementation details.
897
898Currently, \CFA generic types allow assertion.
899For example, the following declaration of a sorted set-type ensures the set key supports equality and relational comparison.
900\begin{cfa}
901forall( Elem, @Key@ | { _Bool ?==?( Key, Key ); _Bool ?<?( Key, Key ); } )
902struct Sorted_Set { Elem elem; @Key@ key; ... };
903\end{cfa}
904However, the operations that insert/remove elements from the set should not appear as part of the generic-types assertions.
905\begin{cfa}
906forall( @Elem@ | /* any assertions on element type */ ) {
907 void insert( Sorted_Set set, @Elem@ elem ) { ... }
908 bool remove( Sorted_Set set, @Elem@ elem ) { ... } // false => element not present
909 ... // more set operations
910} // distribution
911\end{cfa}
912(Note, the @forall@ clause can be distributed across multiple functions.)
913For software-engineering reasons, the set assertions would be refactored into a trait to allow alternative implementations, like a Java \lstinline[language=java]{interface}.
914
915In summation, the \CFA type system inherits \newterm{nominal typing} for concrete types from C;
916however, without inheritance in \CFA, nominal typing cannot be extended to polymorphic subtyping.
917Instead, \CFA adds \newterm{structural typing} and uses it to generate polymorphism.
918Here, traits are like interfaces in Java or abstract base-classes in \CC, but without the nominal inheritance relationships.
919Instead, each polymorphic function or generic type defines the structural requirements needed for its execution, which is fulfilled at each call site from the lexical environment, like Go~\cite{Go} or Rust~\cite{Rust} interfaces.
920Hence, lexical scopes and nested functions are used extensively to mimic subtypes, as in the @qsort@ example, without managing a nominal inheritance hierarchy.
921
922
923\section{Language Comparison}
924
925\begin{table}
926\caption{Overload Features in Programming Languages}
927\label{t:OverloadingFeatures}
928\centering
929\begin{minipage}{\linewidth}
930\setlength{\tabcolsep}{5pt}
931\begin{tabular}{@{}r|cccccccc@{}}
932Feature\,{\textbackslash}\,Language & Ada & \CC & \CFA & Java & Scala & Swift & Rust & Haskell \\
933\hline
934general\footnote{overloadable entities: V $\Rightarrow$ variable, O $\Rightarrow$ operator, F $\Rightarrow$ function, M $\Rightarrow$ member}
935 & O\footnote{except assignment}/F & O/F/M & V/O/F & M\footnote{not universal} & O/M & O/F/M & no & no \\
936general constraints\footnote{T $\Rightarrow$ parameter type, \# $\Rightarrow$ parameter count, N $\Rightarrow$ parameter name; R $\Rightarrow$ return type}
937 & T/\#//R\footnote{parameter names can be used to disambiguate among overloads but not create overloads}
938 & T/\# & T/\#/R & T/\# & T/\#/N/R & T/\#/N/R & T/\#/N & T/R \\
939univ. type constraints\footnote{A $\Rightarrow$ concept, T $\Rightarrow$ interface/trait/type-class, B $\Rightarrow$ type bounds}
940 & no\footnote{generic cannot be overloaded} & C & T & B & B & T & T & T \\
941Safe/Unsafe arg. conv. & no & S/U\footnote{no conversions allowed during template parameter deduction} & S/U
942 & S\footnote{unsafe (narrowing) conversion only allowed in assignment or initialization to a primitive (builtin) type} & S
943 & no\footnote{literals only, Int $\rightarrow$ Double (Safe)} & no & no
944\end{tabular}
945\end{minipage}
946% https://doc.rust-lang.org/rust-by-example/trait/impl_trait.html
947% https://dl.acm.org/doi/10.1145/75277.75283
948\end{table}
949
950Because \CFA's type system is focused on overloading and overload resolution, \VRef[Table]{t:OverloadingFeatures} compares \CFA with a representative set of popular programming languages and their take on overloading.
951The first row classifies whether there is general overloading, and what entities may be overloaded.
952\begin{cquote}
953\setlength{\tabcolsep}{10pt}
954\begin{tabular}{@{}llll@{}}
955\multicolumn{1}{c}{\textbf{variable}} & \multicolumn{1}{c}{\textbf{operator}} & \multicolumn{1}{c}{\textbf{function}} & \multicolumn{1}{c}{\textbf{member}} \\
956\begin{cfa}
957int foo;
958double foo;
959
960
961\end{cfa}
962&
963\begin{cfa}
964int ?+?( int );
965double ?+?( double );
966
967
968\end{cfa}
969&
970\begin{cfa}
971int foo( int );
972double foo( int, int );
973
974
975\end{cfa}
976&
977\begin{c++}
978class C {
979 int foo( int );
980 double foo( int, int );
981};
982\end{c++}
983\end{tabular}
984\end{cquote}
985
986The second row classifies the specialization mechanisms used to distinguish among the general overload capabilities.
987\begin{cquote}
988\begin{tabular}{@{}llll@{}}
989\multicolumn{1}{c}{\textbf{type}} & \multicolumn{1}{c}{\textbf{number}} & \multicolumn{1}{c}{\textbf{name}} & \multicolumn{1}{c}{\textbf{return}} \\
990\begin{swift}
991func foo( _ x : Int )
992func foo( _ x : Double )
993foo( 3 )
994foo( 3.5 )
995\end{swift}
996&
997\begin{swift}
998func foo( _ x : Int )
999func foo( _ x : Int, _ y : Int )
1000foo( 3 )
1001foo( 3, 3 )
1002\end{swift}
1003&
1004\begin{swift}
1005func foo( x : Int )
1006func foo( y : Int )
1007foo( x : 3 )
1008foo( y : 3 );
1009\end{swift}
1010&
1011\begin{swift}
1012func foo() -> Int
1013func foo() -> String
1014var i : Int = foo()
1015var s : String = foo();
1016\end{swift}
1017\end{tabular}
1018\end{cquote}
1019
1020The third row classifies if generic functions can be overloaded based on differing type variables, number of type variables, and type-variable constraints.
1021The following examples illustrates the first two overloading cases.
1022\begin{swift}
1023func foo<T>( a : T ); $\C[2.25in]{// foo1}$
1024func foo<T>( a : T, b : T ); $\C{// foo2}$
1025func foo<T,U>( a : T, b : U ); $\C{// foo3}$
1026foo( a : 3.5 ); $\C{// foo1}$
1027foo( a : 2, b : 2 ); $\C{// foo2}$
1028foo( a : 2, b : 2.5 ); $\C{// foo3}\CRT$
1029\end{swift}
1030Here, the overloaded calls are disambiguated using argument types and their number.
1031
1032However, the parameter operations are severely restricted because universal types have few operations.
1033For example, Swift provides a @print@ operation for its universal type, and the Java @Object@ class provides general methods: @toString@, @hashCode@, @equals@, @finalize@, \etc.
1034This restricted mechanism still supports a few useful functions, where the parameters are abstract entities, \eg:
1035\begin{swift}
1036func swap<T>( _ a : inout T, _ b : inout T ) { let temp : T = a; a = b; b = temp; }
1037var ix = 4, iy = 3;
1038swap( &ix, &iy );
1039var fx = 4.5, fy = 3.5;
1040swap( &fx, &fy );
1041\end{swift}
1042To make a universal function useable, an abstract description is needed for the operations used on the parameters within the function body.
1043Type matching these operations can be done by using techniques like \CC template expansion, or explicit stating, \eg interfaces, subtyping (inheritance), assertions (traits), type classes, type bounds.
1044The mechanism chosen can affect separate compilation or require runtime type information (RTTI).
1045\begin{description}
1046\item[concept] ~
1047\begin{c++}
1048concept range = requires( T& t ) {
1049 ranges::begin(t); // equality-preserving for forward iterators
1050 ranges::end (t);
1051};
1052\end{c++}
1053\item[traits/type-classes] ~
1054\begin{cfa}
1055forall( T | { T ?+?( T, T ) } )
1056\end{cfa}
1057\item[inheritance/type bounds] ~
1058\begin{scala}
1059class CarPark[Vehicle >: bike <: bus](val lot : Vehicle)
1060\end{scala}
1061\end{description}
1062
1063\begin{figure}
1064\setlength{\tabcolsep}{12pt}
1065\begin{tabular}{@{}ll@{}}
1066\multicolumn{1}{c}{\textbf{\CFA}} & \multicolumn{1}{c}{\textbf{Haskell}} \\
1067\begin{cfa}
1068forall( T ) trait sumable {
1069 void ?{}( T &, zero_t );
1070 T ?+=?( T &, T ); };
1071forall( T | sumable( T ) )
1072T sum( T a[], size_t size ) {
1073 T total = 0;
1074 for ( i; size ) total += a[i];
1075 return total; }
1076struct S { int i, j; };
1077void ?{}( S & s, zero_t ) { s.[i, j] = 0; }
1078void ?{}( S & s ) { s{0}; }
1079void ?{}( S & s, int i, int j ) { s.[i, j] = [i, j]; }
1080S ?+=?( S & l, S r ) { l.[i, j] += r.[i, j]; }
1081int main() { // trait inferencing
1082 sout | sum( (int []){ 1, 2, 3 }, 3 );
1083 sout | sum( (double []){ 1.5, 2.5, 3.5 }, 3 );
1084 sout | sum( (S []){ {1,1}, {2,2}, {3,3} }, 3 ).[i, j]; }
1085
1086
1087
1088\end{cfa}
1089&
1090\begin{haskell}
1091class Sumable a where
1092 szero :: a
1093 sadd :: a -> a -> a
1094ssum :: Sumable a $=>$ [a] -> a
1095ssum (x:xs) = sadd x (ssum xs)
1096ssum [] = szero
1097data S = S Int Int deriving Show
1098@instance Sumable Int@ where
1099 szero = 0
1100 sadd = (+)
1101@instance Sumable Float@ where
1102 szero = 0.0
1103 sadd = (+)
1104@instance Sumable S@ where
1105 szero = S 0 0
1106 sadd (S l1 l2) (S r1 r2) = S (l1 + r1) (l2 + r2)
1107main = do
1108 print ( ssum [1::Int, 2, 3] )
1109 print ( ssum [1.5::Float, 2.5, 3.5] )
1110 print ( ssum [(S 1 1), (S 2 2), (S 3 3)] )
1111\end{haskell}
1112\end{tabular}
1113\caption{Implicit/Explicit Trait Inferencing}
1114\label{f:ImplicitExplicitTraitInferencing}
1115\end{figure}
1116
1117One differentiating feature among these specialization techniques is the ability to implicitly or explicitly infer the trait information at a class site.
1118\VRef[Figure]{f:ImplicitExplicitTraitInferencing} compares the @sumable@ trait and polymorphic @sum@ function \see{\VRef{s:Traits}} for \CFA and Haskell.
1119Here, the \CFA type system inferences the trait functions at each call site, so no additional specification is necessary by the programmer.
1120The Haskell program requires the programmer to explicitly bind the trait and to each type that can be summed.
1121(The extra casts in the Haskell program are necessary to differentiate between the two kinds of integers and floating-point numbers.)
1122Ada also requires explicit binding, creating a new function name for each generic instantiation.
1123\begin{ada}
1124function int_Twice is new Twice( Integer, "+" => "+" );
1125function float_Twice is new Twice( Float, "+" => "+" );
1126\end{ada}
1127Finally, there is a belief that certain type systems cannot support general overloading, \eg Haskell.
1128As \VRef[Table]{t:OverloadingFeatures} shows, there are multiple languages with both general and parametric overloading, so the decision to not support general overloading is based on design choices made by the language designers not any reason in type theory.
1129
1130The fourth row classifies if conversions are attempted beyond exact match.
1131\begin{cfa}
1132int foo( double ); $\C[1.75in]{// 1}$
1133double foo( int ); $\C{// 2}$
1134int i = foo( 3 ); $\C{// 1 : 3 to 3.0 argument conversion => all conversions are safe}$
1135double d = foo( 3.5 ); $\C{// 1 : int to double result conversion => all conversions are safe}\CRT$
1136\end{cfa}
1137
1138
1139\section{Contributions}
1140
1141The \CFA compiler performance and type capability have been greatly improved through my development work.
1142\begin{enumerate}
1143\item
1144The compilation time of various \CFA library units and test programs has been reduced by an order of magnitude, from minutes to seconds \see{\VRef[Table]{t:SelectedFileByCompilerBuild}}, which made it possible to develop and test more complicated \CFA programs that utilize sophisticated type system features.
1145The details of compiler optimization work are covered in a previous technical report~\cite{Yu20}, which essentially forms part of this thesis.
1146\item
1147This thesis presents a systematic review of the new features added to the \CFA language and its type system.
1148Some of the more recent inclusions to \CFA, such as tuples and generic structure types, were not well tested during development due to the limitation of compiler performance.
1149Several issues coming from the interactions of various language features are identified and discussed in this thesis;
1150some of them I have resolved, while others are given temporary fixes and need to be reworked in the future.
1151\item
1152Finally, this thesis provides constructive ideas for fixing a number of high-level issues in the \CFA language design and implementation, and gives a path for future improvements to the language and compiler.
1153\end{enumerate}
1154
1155
1156\begin{comment}
1157From: Andrew James Beach <ajbeach@uwaterloo.ca>
1158To: Peter Buhr <pabuhr@uwaterloo.ca>, Michael Leslie Brooks <mlbrooks@uwaterloo.ca>,
1159 Fangren Yu <f37yu@uwaterloo.ca>, Jiada Liang <j82liang@uwaterloo.ca>
1160Subject: Re: Haskell
1161Date: Fri, 30 Aug 2024 16:09:06 +0000
1162
1163Do you mean:
1164
1165one = 1
1166
1167And then write a bunch of code that assumes it is an Int or Integer (which are roughly int and Int in Cforall) and then replace it with:
1168
1169one = 1.0
1170
1171And have that crash? That is actually enough, for some reason Haskell is happy to narrow the type of the first literal (Num a => a) down to Integer but will not do the same for (Fractional a => a) and Rational (which is roughly Integer for real numbers). Possibly a compatibility thing since before Haskell had polymorphic literals.
1172
1173Now, writing even the first version will fire a -Wmissing-signatures warning, because it does appear to be understood that just from a documentation perspective, people want to know what types are being used. Now, if you have the original case and start updating the signatures (adding one :: Fractional a => a), you can eventually get into issues, for example:
1174
1175import Data.Array (Array, Ix, (!))
1176atOne :: (Ix a, Frational a) => Array a b -> b - - In CFA: forall(a | Ix(a) | Frational(a), b) b atOne(Array(a, b) const & array)
1177atOne = (! one)
1178
1179Which compiles and is fine except for the slightly awkward fact that I don't know of any types that are both Ix and Fractional types. So you might never be able to find a way to actually use that function. If that is good enough you can reduce that to three lines and use it.
1180
1181Something that just occurred to me, after I did the above examples, is: Are there any classic examples in literature I could adapt to Haskell?
1182
1183Andrew
1184
1185PS, I think it is too obvious of a significant change to work as a good example but I did mock up the structure of what I am thinking you are thinking about with a function. If this helps here it is.
1186
1187doubleInt :: Int -> Int
1188doubleInt x = x * 2
1189
1190doubleStr :: String -> String
1191doubleStr x = x ++ x
1192
1193-- Missing Signature
1194action = doubleInt - replace with doubleStr
1195
1196main :: IO ()
1197main = print $ action 4
1198\end{comment}
Note: See TracBrowser for help on using the repository browser.