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

Last change on this file since bc999b7 was bc999b7, checked in by Peter A. Buhr <pabuhr@…>, 10 days ago

more proofreading of intro chapter

  • Property mode set to 100644
File size: 19.3 KB
Line 
1\chapter{Introduction}
2
3This thesis is exploratory work I did to understand, fix, and extend the \CFA type-system, specifically, the type resolver used to select polymorphic types among overloaded names.
4Overloading allows programmers to use the most meaningful names without fear of name clashes within a program or from external sources, like include files.
5\begin{quote}
6There are only two hard things in Computer Science: cache invalidation and \emph{naming things}. --- Phil Karlton
7\end{quote}
8Experience from \CC and \CFA developers is that the type system implicitly and correctly disambiguates the majority of overloaded names, \ie it is rare to get an incorrect selection or ambiguity, even among hundreds of overloaded (variables and) functions.
9In many cases, a programmer has no idea there are name clashes, as they are silently resolved, simplifying the development process.
10Depending on the language, any ambiguous cases are resolved using some form of qualification and/or casting.
11
12Therefore, one of the key goals in \CFA is to push the boundary on overloading, and hence, overload resolution.
13As well, \CFA follows the current trend of replacing nominal inheritance with traits.
14Together, the resulting \CFA type-system has a number of unique features making it different from all other programming languages.
15
16
17\section{Types}
18
19\begin{quote}
20Some are born great, some achieve greatness, and some have greatness thrust upon them. Twelfth Night, Act II Scene 5, William Shakespeare
21\end{quote}
22
23All 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.
24A programming language and its compiler present ways to declare types that ultimately map into the ones provided by the underlying hardware.
25These language types are thrust upon programmers, with their syntactic and semantic rules and restrictions.
26These rules are used to transform a language expression to a hardware expression.
27Modern programming-languages allow user-defined types and generalize across multiple types using polymorphism.
28Type systems can be static, where each variable has a fixed type during execution and an expression's type is determined once at compile time, or dynamic, where each variable can change type during execution and so an expression's type is reconstructed on each evaluation.
29Expressibility, 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.
30
31
32\section{Operator Overloading}
33
34Virtually all programming languages overload the arithmetic operators across the basic computational types using the number and type of parameters and returns.
35Like \CC, \CFA also allows these operators to be overloaded with user-defined types.
36The syntax for operator names uses the @'?'@ character to denote a parameter, \eg unary left and right operators: @?++@ and @++?@, and binary operators @?+?@ and @?<=?@.
37Here, a user-defined type is extended with an addition operation with the same syntax as builtin types.
38\begin{cfa}
39struct S { int i, j };
40S @?+?@( S op1, S op2 ) { return (S){ op1.i + op2.i, op1.j + op2.j }; }
41S s1, s2;
42s1 = s1 @+@ s2;                 $\C[1.75in]{// infix call}$
43s1 = @?+?@( s1, s2 );   $\C{// direct call}\CRT$
44\end{cfa}
45The type system examines each call site and selects the best matching overloaded function based on the number and types of arguments.
46If there are mixed-mode operands, @2 + 3.5@, the type system, like in C/\CC, attempts (safe) conversions, converting the argument type(s) to the parameter type(s).
47Conversions are necessary because the hardware rarely supports mix-mode operations, so both operands must be the same type.
48Note, without implicit conversions, programmers must write an exponential number of functions covering all possible exact-match cases among all possible types.
49This approach does not match with programmer intuition and expectation, regardless of any \emph{safety} issues resulting from converted values.
50
51
52\section{Function Overloading}
53
54Both \CFA and \CC allow function names to be overloaded, as long as their prototypes differ in the number and type of parameters and returns.
55\begin{cfa}
56void f( void );                 $\C[2in]{// (1): no parameter}$
57void f( char );                 $\C{// (2): overloaded on the number and parameter type}$
58void f( int, int );             $\C{// (3): overloaded on the number and parameter type}$
59f( 'A' );                               $\C{// select (2)}\CRT$
60\end{cfa}
61In this case, the name @f@ is overloaded depending on the number and parameter types.
62The type system examines each call size and selects the best match based on the number and types of the arguments.
63Here, there is a perfect match for the call, @f( 'A' )@ with the number and parameter type of function (2).
64
65Ada, Scala, and \CFA type-systems also use the return type in resolving a call, to pinpoint the best overloaded name.
66For example, in many programming languages with overloading, the following functions are ambiguous without using the return type.
67\begin{cfa}
68int f( int );                   $\C[2in]{// (1); overloaded on return type and parameter}$
69double f( int );                $\C{// (2); overloaded on return type and parameter}$
70int i = f( 3 );                 $\C{// select (1)}$
71double d = f( 3 );              $\C{// select (2)}\CRT$
72\end{cfa}
73Alternatively, if the type system looks at the return type, there is an exact match for each call, which matches with programmer intuition and expectation.
74This capability can be taken to the extreme, where there are no function parameters.
75\begin{cfa}
76int random( void );             $\C[2in]{// (1); overloaded on return type}$
77double random( void );  $\C{// (2); overloaded on return type}$
78int i = random();               $\C{// select (1)}$
79double d = random();    $\C{// select (2)}\CRT$
80\end{cfa}
81Again, there is an exact match for each call.
82If there is no exact match, a set of minimal conversions can be added to find a best match, as for operator overloading.
83
84
85\section{Variable Overloading}
86
87Unlike most programming languages, \CFA has variable overloading within a scope, along with shadow overloading in nested scopes.
88(Shadow overloading is also possible for functions, if a language supports nested function declarations, \eg \CC named, nested, lambda functions.)
89\begin{cfa}
90void foo( double d );
91int v;                              $\C[2in]{// (1)}$
92double v;                               $\C{// (2) variable overloading}$
93foo( v );                               $\C{// select (2)}$
94{
95        int v;                          $\C{// (3) shadow overloading}$
96        double v;                       $\C{// (4) and variable overloading}$
97        foo( v );                       $\C{// select (4)}\CRT$
98}
99\end{cfa}
100It is interesting that shadow overloading is considered a normal programming-language feature with only slight software-engineering problems.
101However, variable overloading within a scope is considered extremely dangerous, without any evidence to corroborate this claim.
102Similarly, function overloading occurs silently within the global scope in \CC from @#include@ files all the time without problems.
103
104In \CFA, the type system simply treats overloaded variables as an overloaded function returning a value with no parameters.
105Hence, no significant effort is required to support this feature by leveraging the return type to disambiguate as variables have no parameters.
106\begin{cfa}
107int MAX = 2147483647;   $\C[2in]{// (1); overloaded on return type}$
108long int MAX = ...;             $\C{// (2); overloaded on return type}$
109double MAX = ...;               $\C{// (3); overloaded on return type}$
110int i = MAX;                    $\C{// select (1)}$
111long int i = MAX;               $\C{// select (2)}$
112double d = MAX;                 $\C{// select (3)}\CRT$
113\end{cfa}
114Hence, the name @MAX@ can replace all the C type-specific names, \eg @INT_MAX@, @LONG_MAX@, @DBL_MAX@, \etc.
115The result is a significant reduction in names to access typed constants.
116% Paraphrasing Shakespeare, ``The \emph{name} is the thing.''.
117
118
119\section{Constant Overloading}
120
121\CFA is unique in providing restricted constant overloading for the values @0@ and @1@, which have special status in C, \eg the value @0@ is both an integer and a pointer literal, so its meaning depends on context.
122In addition, several operations are defined in terms of values @0@ and @1@, \eg:
123\begin{cfa}
124if ( x ) ++x                    $\C{// if ( x != 0 ) x += 1;}$
125\end{cfa}
126Every @if@ and iteration statement in C compares the condition with @0@, and every increment and decrement operator is semantically equivalent to adding or subtracting the value @1@ and storing the result.
127These two constants are given types @zero_t@ and @one_t@ in \CFA, which allows overloading various operations for new types that seamlessly connect to all special @0@ and @1@ contexts.
128The 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.
129\begin{cfa}
130struct S { int i, j; };
131void ?{}( S & s, zero_t ) { s.[i,j] = 0; } $\C{// constructors}$
132void ?{}( S & s, one_t ) { s.[i,j] = 1; }
133S ?=?( S & dst, zero_t ) { dst.[i,j] = 0; return dst; } $\C{// assignment}$
134S ?=?( S & dst, one_t ) { dst.[i,j] = 1; return dst; }
135S ?+=?( S & s, one_t ) { s.[i,j] += 1; return s; } $\C{// increment/decrement}$
136S ?-=?( S & s, one_t ) { s.[i,j] -= 1; return s; }
137int ?!=?( S s, zero_t ) { return s.i != 0 && s.j != 0; } $\C{// comparison}$
138S s = @0@;
139s = @0@;
140s = @1@;
141if ( @s@ ) @++s@;       $\C{// unary ++/-\,- come from +=/-=}$
142\end{cfa}
143Hence, type @S@ is first-class with respect to the basic types, working with all existing implicit C mechanisms.
144
145
146\section{Type Inferencing}
147
148Every variable has a type, but association between them can occur in different ways:
149at the point where the variable comes into existence (declaration) and/or on each assignment to the variable.
150\begin{cfa}
151double x;                               $\C{// type only}$
152float y = 3.1D;                 $\C{// type and initialization}$
153auto z = y;                             $\C{// initialization only}$
154z = "abc";                              $\C{// assignment}$
155\end{cfa}
156For type-only, the programmer specifies the initial type, which remains fixed for the variable's lifetime in statically typed languages.
157For type-and-initialization, the specified and initialization types may not agree.
158For initialization-only, the compiler may select the type by melding programmer and context information.
159When the compiler participates in type selection, it is called \newterm{type inferencing}.
160Note, type inferencing is different from type conversion: type inferencing \emph{discovers} a variable's type before setting its value, whereas conversion has two typed values and performs a (possibly lossy) action to convert one value to the type of the other variable.
161Finally, for assignment, the current variable and expression types may not agree.
162
163One of the first and powerful type-inferencing system is Hindley--Milner~\cite{Damas82}.
164Here, 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.
165\begin{cfa}
166auto f() {
167        x = 1;   y = 3.5;       $\C{// set types from constants}$
168        x = // expression involving x, y and other local initialized variables
169        y = // expression involving x, y and other local initialized variables
170        return x, y;
171}
172auto w = f();                   $\C{// typing flows outwards}$
173
174void f( auto x, auto y ) {
175        x = // expression involving x, y and other local initialized variables
176        y = // expression involving x, y and other local initialized variables
177}
178s = 1;   t = 3.5;               $\C{// set types from constants}$
179f( s, t );                              $\C{// typing flows inwards}$
180\end{cfa}
181In 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.
182Note, like template meta-programming, there could 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@.
183Inferring type constraints, by analysing the body of @f@ is possible, and these constraints must be satisfied at each call site by the argument types;
184in this case, parametric polymorphism can allow separate compilation.
185In languages with type inferencing, there is often limited overloading to reduce the search space, which introduces the naming problem.
186Note, 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.
187
188In simpler type-inferencing systems, such as C/\CC/\CFA, there are more specific usages.
189\begin{cquote}
190\setlength{\tabcolsep}{10pt}
191\begin{tabular}{@{}lll@{}}
192\multicolumn{1}{c}{\textbf{gcc / \CFA}} & \multicolumn{1}{c}{\textbf{\CC}} \\
193\begin{cfa}
194#define expr 3.0 * i
195typeof(expr) x = expr;
196int y;
197typeof(y) z = y;
198\end{cfa}
199&
200\begin{cfa}
201
202auto x = 3.0 * 4;
203int y;
204auto z = y;
205\end{cfa}
206&
207\begin{cfa}
208
209// use type of initialization expression
210
211// use type of initialization expression
212\end{cfa}
213\end{tabular}
214\end{cquote}
215The two important capabilities are:
216\begin{itemize}[topsep=0pt]
217\item
218Not determining or writing long generic types, \eg, given deeply nested generic types.
219\begin{cfa}
220typedef T1(int).T2(float).T3(char).T @ST@;  $\C{// \CFA nested type declaration}$
221@ST@ x, y, x;
222\end{cfa}
223This issue is exaggerated with \CC templates, where type names are 100s of characters long, resulting in unreadable error messages.
224\item
225Ensuring the type of secondary variables, always match a primary variable.
226\begin{cfa}
227int x; $\C{// primary variable}$
228typeof(x) y, z, w; $\C{// secondary variables match x's type}$
229\end{cfa}
230If the type of @x@ changes, the type of the secondary variables correspondingly updates.
231\end{itemize}
232Note, the use of @typeof@ is more restrictive, and possibly safer, than general type-inferencing.
233\begin{cfa}
234int x;
235type(x) y = ... // complex expression
236type(x) z = ... // complex expression
237\end{cfa}
238Here, the types of @y@ and @z@ are fixed (branded), whereas with type inferencing, the types of @y@ and @z@ are potentially unknown.
239
240
241\section{Type-Inferencing Issues}
242
243Each kind of type-inferencing system has its own set of issues that flow onto the programmer in the form of convenience, restrictions, or confusions.
244
245A 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.
246
247A restriction is the conundrum in type inferencing of when to \emph{brand} a type.
248That is, when is the type of the variable/function more important than the type of its initialization expression.
249For example, if a change is made in an initialization expression, it can cause cascading type changes and/or errors.
250At some point, a variable's type needs to remain constant and the initializing expression needs to be modified or in error when it changes.
251Often type-inferencing systems allow restricting (\newterm{branding}) a variable or function type, so the complier can report a mismatch with the constant initialization.
252\begin{cfa}
253void f( @int@ x, @int@ y ) {  // brand function prototype
254        x = // expression involving x, y and other local initialized variables
255        y = // expression involving x, y and other local initialized variables
256}
257s = 1;   t = 3.5;
258f( s, @t@ ); // type mismatch
259\end{cfa}
260In Haskell, it is common for programmers to brand (type) function parameters.
261
262A confusion is large blocks of code where all declarations are @auto@, as is now common in \CC.
263As a result, understanding and changing the code becomes almost impossible.
264Types provide important clues as to the behaviour of the code, and correspondingly to correctly change or add new code.
265In these cases, a programmer is forced to re-engineer types, which is fragile, or rely on a fancy IDE that can re-engineer types for them.
266For example, given:
267\begin{cfa}
268auto x = @...@
269\end{cfa}
270and the need to write a routine to compute using @x@
271\begin{cfa}
272void rtn( @type of x@ parm );
273rtn( x );
274\end{cfa}
275A programmer must re-engineer the type of @x@'s initialization expression, reconstructing the possibly long generic type-name.
276In this situation, having the type name or its short alias is essential.
277
278The \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 the right types within an expression.
279Type inferencing defeats this goal because there is no left-hand type.
280Fundamentally, type inferencing tries to magic away variable types from the programmer.
281However, this results in lazy programming with the potential for poor performance and safety concerns.
282Types are as important as control-flow in writing a good program, and should not be masked, even if it requires the programmer to think!
283A similar issue is garbage collection, where storage management is magicked away, often resulting in poor program design and performance.\footnote{
284There are full-time Java consultants, who are hired to find memory-management problems in large Java programs.}
285The entire area of Computer-Science data-structures is obsessed with time and space, and that obsession should continue into regular programming.
286Understanding space and time issues are an essential part of the programming craft.
287Given @typedef@ and @typeof@ in \CFA, and the strong need to use the left-hand type in resolution, implicit type-inferencing is unsupported.
288Should a significant need arise, this feature can be revisited.
289
290
291\section{Polymorphism}
292
293
294
295\section{Contributions}
296
297
298
299\begin{comment}
300From: Andrew James Beach <ajbeach@uwaterloo.ca>
301To: Peter Buhr <pabuhr@uwaterloo.ca>, Michael Leslie Brooks <mlbrooks@uwaterloo.ca>,
302    Fangren Yu <f37yu@uwaterloo.ca>, Jiada Liang <j82liang@uwaterloo.ca>
303Subject: Re: Haskell
304Date: Fri, 30 Aug 2024 16:09:06 +0000
305
306Do you mean:
307
308one = 1
309
310And 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:
311
312one = 1.0
313
314And 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.
315
316Now, 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:
317
318import Data.Array (Array, Ix, (!))
319atOne :: (Ix a, Frational a) => Array a b -> b - - In CFA: forall(a | Ix(a) | Frational(a), b) b atOne(Array(a, b) const & array)
320atOne = (! one)
321
322Which 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.
323
324Something that just occurred to me, after I did the above examples, is: Are there any classic examples in literature I could adapt to Haskell?
325
326Andrew
327
328PS, 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.
329
330doubleInt :: Int -> Int
331doubleInt x = x * 2
332
333doubleStr :: String -> String
334doubleStr x = x ++ x
335
336-- Missing Signature
337action = doubleInt - replace with doubleStr
338
339main :: IO ()
340main = print $ action 4
341\end{comment}
Note: See TracBrowser for help on using the repository browser.