Bladeren bron

Merge branch 'master' of github.com:IUCompilerCourse/Essentials-of-Compilation

Jeremy Siek 3 jaren geleden
bovenliggende
commit
fb2103fb81
1 gewijzigde bestanden met toevoegingen van 63 en 60 verwijderingen
  1. 63 60
      book.tex

+ 63 - 60
book.tex

@@ -13115,10 +13115,12 @@ class TypeCheckLarray(TypeCheckLtup):
         self.check_type_equal(index_ty, IntType(), index)
         self.check_type_equal(index_ty, IntType(), index)
         match tup_t:
         match tup_t:
           case ListType(ty):
           case ListType(ty):
-            self.check_type_equal(ty, value_t, ss[0])          
+            self.check_type_equal(ty, value_t, ss[0])
+          case TupleType(ts):
+            return self.type_check_stmts(ss, env)
           case _:
           case _:
-            raise Exception('type_check_stmts: expected a list, not ' \
-                            + repr(tup_t))
+            raise Exception('type_check_stmts: '
+              'expected tuple or list, not ' + repr(tup_t))
         return self.type_check_stmts(ss[1:], env)
         return self.type_check_stmts(ss[1:], env)
       case _:
       case _:
         return super().type_check_stmts(ss, env)
         return super().type_check_stmts(ss, env)
@@ -13206,10 +13208,10 @@ However, we limit tuples to a length of $50$ so that their length and
 pointer mask can fit into the 64-bit tag at the beginning of each
 pointer mask can fit into the 64-bit tag at the beginning of each
 tuple (Section~\ref{sec:data-rep-gc}). We intend arrays to allow
 tuple (Section~\ref{sec:data-rep-gc}). We intend arrays to allow
 millions of elements, so we need more bits to store the length.
 millions of elements, so we need more bits to store the length.
-However, because arrays are homogeneous, we only need $1$ bit for the
-pointer mask instead of one bit per array elements.  Finally, the
-garbage collector will need to be able to distinguish between tuples
-and arrays, so we need to reserve $1$ bit for that purpose.  So we
+However, because arrays are homogeneous, we only need one bit for the
+pointer mask instead of one bit per array element.  Finally, the
+garbage collector must be able to distinguish between tuples
+and arrays, so we need to reserve one bit for that purpose.  We
 arrive at the following layout for the 64-bit tag at the beginning of
 arrive at the following layout for the 64-bit tag at the beginning of
 an array:
 an array:
 \begin{itemize}
 \begin{itemize}
@@ -13311,8 +13313,9 @@ operations. In particular, the new AST node \ALLOCARRAY{\Exp}{\Type}
 is analogous to the \code{Allocate} AST node for tuples.  The $\Type$
 is analogous to the \code{Allocate} AST node for tuples.  The $\Type$
 argument must be \ARRAYTY{T} where $T$ is the element type for the
 argument must be \ARRAYTY{T} where $T$ is the element type for the
 array. The \code{AllocateArray} AST node allocates an array of the
 array. The \code{AllocateArray} AST node allocates an array of the
-length specified by the $\Exp$ but does not initialize the elements of
-the array. Generate code in this pass to initialize the elements.
+length specified by the $\Exp$ (of type \INTTY), but does not initialize the elements of
+the array. Generate code in this pass to initialize the elements
+analogous to the case for tuples.
 
 
 \subsection{Remove Complex Operands}
 \subsection{Remove Complex Operands}
 
 
@@ -18743,6 +18746,7 @@ movq 0(%r11) |$\itm{lhs'}$|
 \fi}
 \fi}
 \end{minipage}
 \end{minipage}
 \end{center}
 \end{center}
+% $ pacify font lock
 
 
 %% \paragraph{\racket{\code{any-vector-set!}}\python{\code{any\_tuple\_store}}}
 %% \paragraph{\racket{\code{any-vector-set!}}\python{\code{any\_tuple\_store}}}
 
 
@@ -18909,7 +18913,7 @@ The concrete syntax of \LangGrad{} is defined in
 Figure~\ref{fig:Lgrad-concrete-syntax} and its abstract syntax is
 Figure~\ref{fig:Lgrad-concrete-syntax} and its abstract syntax is
 defined in Figure~\ref{fig:Lgrad-syntax}. The main syntactic
 defined in Figure~\ref{fig:Lgrad-syntax}. The main syntactic
 difference between \LangLam{} and \LangGrad{} is that type annotations
 difference between \LangLam{} and \LangGrad{} is that type annotations
-optional, which is specified in the grammar using the \Param{} and
+are optional, which is specified in the grammar using the \Param{} and
 \itm{ret} non-terminals. In the abstract syntax, type annotations are
 \itm{ret} non-terminals. In the abstract syntax, type annotations are
 not optional but we use the \CANYTY{} type when a type annotation is
 not optional but we use the \CANYTY{} type when a type annotation is
 absent.
 absent.
@@ -19084,7 +19088,7 @@ tuple. The \code{inc} function has type
 \racket{\code{(Any -> Any)}}\python{\code{Callable[[Any],Any]}}
 \racket{\code{(Any -> Any)}}\python{\code{Callable[[Any],Any]}}
 but parameter \code{f} of \code{map} has type
 but parameter \code{f} of \code{map} has type
 \racket{\code{(Integer -> Integer)}}\python{\code{Callable[[int],int]}}.
 \racket{\code{(Integer -> Integer)}}\python{\code{Callable[[int],int]}}.
-The type checker for \LangGrad{} allows this because the two types are
+The type checker for \LangGrad{} accepts this call because the two types are
 consistent.
 consistent.
 
 
 \begin{figure}[btp]
 \begin{figure}[btp]
@@ -19149,10 +19153,9 @@ print(t[1])
         case (_, AnyType()):
         case (_, AnyType()):
           return True
           return True
         case (FunctionType(ps1, rt1), FunctionType(ps2, rt2)):
         case (FunctionType(ps1, rt1), FunctionType(ps2, rt2)):
-          return all([self.consistent(p1, p2) for (p1,p2) in zip(ps1,ps2)]) \
-              and consistent(rt1, rt2)
+          return all(map(self.consistent, ps1, ps2)) and consistent(rt1, rt2)
         case (TupleType(ts1), TupleType(ts2)):
         case (TupleType(ts1), TupleType(ts2)):
-          return all([self.consistent(ty1, ty2) for (ty1,ty2) in zip(ts1,ts2)])
+          return all(map(self.consistent, ts1, ts2))
         case (_, _):
         case (_, _):
           return t1 == t2
           return t1 == t2
 \end{lstlisting}  
 \end{lstlisting}  
@@ -19311,11 +19314,11 @@ print(t[1])
 \begin{tcolorbox}[colback=white]  
 \begin{tcolorbox}[colback=white]  
 \begin{lstlisting}[basicstyle=\ttfamily\footnotesize]
 \begin{lstlisting}[basicstyle=\ttfamily\footnotesize]
 class TypeCheckLgrad(TypeCheckLlambda):
 class TypeCheckLgrad(TypeCheckLlambda):
-  def type_check_exp(self, e, env):
+  def type_check_exp(self, e, env) -> Type:
     match e:
     match e:
       case Name(id):
       case Name(id):
         return env[id]
         return env[id]
-      case Constant(value) if value is True or value is False:
+      case Constant(value) if isinstance(value, bool):
         return BoolType()
         return BoolType()
       case Constant(value) if isinstance(value, int):
       case Constant(value) if isinstance(value, int):
         return IntType()
         return IntType()
@@ -19336,9 +19339,9 @@ class TypeCheckLgrad(TypeCheckLlambda):
         return self.join_types(body_t, orelse_t)
         return self.join_types(body_t, orelse_t)
       case Call(func, args):
       case Call(func, args):
         func_t = self.type_check_exp(func, env)
         func_t = self.type_check_exp(func, env)
-        args_t = unzip([self.type_check_exp(arg, env) for arg in args])
+        args_t = [self.type_check_exp(arg, env) for arg in args]
         match func_t:
         match func_t:
-          case FunctionType(params_t, return_t):
+          case FunctionType(params_t, return_t) if len(params_t) == len(args_t):
             for (arg_t, param_t) in zip(args_t, params_t):
             for (arg_t, param_t) in zip(args_t, params_t):
                 self.check_consistent(param_t, arg_t, e)
                 self.check_consistent(param_t, arg_t, e)
             return return_t
             return return_t
@@ -19364,15 +19367,14 @@ class TypeCheckLgrad(TypeCheckLlambda):
       case Lambda(params, body):
       case Lambda(params, body):
         match expected_ty:
         match expected_ty:
           case FunctionType(params_t, return_t):
           case FunctionType(params_t, return_t):
-            new_env = {x:t for (x,t) in env.items()}
-            for (p,t) in zip(new_params, params_t):
-                new_env[p] = t
+            new_env = env.copy().update(zip(params, params_t))
             e.has_type = expected_ty
             e.has_type = expected_ty
+            body_ty = self.type_check_exp(body, new_env)
+            self.check_consistent(body_ty, return_t)
           case AnyType():
           case AnyType():
-            new_env = {x:t for (x,t) in env.items()}
-            for p in new_params:
-                new_env[p] = AnyType()
-            e.has_type = FunctionType([AnyType() for _ in new_params], AnyType())
+            new_env = env.copy().update((p, AnyType()) for p in params)
+            e.has_type = FunctionType([AnyType() for _ in params], AnyType())
+            body_ty = self.type_check_exp(body, new_env)
           case _:
           case _:
             raise Exception('lambda does not have type ' + str(expected_ty))
             raise Exception('lambda does not have type ' + str(expected_ty))
       case _:
       case _:
@@ -19394,7 +19396,7 @@ class TypeCheckLgrad(TypeCheckLlambda):
         if id in env:
         if id in env:
           self.check_consistent(env[id], value_ty, value)
           self.check_consistent(env[id], value_ty, value)
         else:
         else:
-          env[id] = t
+          env[id] = value_ty
       ...
       ...
       case _:
       case _:
         raise Exception('type_check_stmts: unexpected ' + repr(ss))
         raise Exception('type_check_stmts: unexpected ' + repr(ss))
@@ -19418,10 +19420,10 @@ class TypeCheckLgrad(TypeCheckLlambda):
         case (_, AnyType()):
         case (_, AnyType()):
           return t1
           return t1
         case (FunctionType(ps1, rt1), FunctionType(ps2, rt2)):
         case (FunctionType(ps1, rt1), FunctionType(ps2, rt2)):
-          return FunctionType([self.join_types(p1, p2) for (p1,p2) in zip(ps1, ps2)],
-                              self.join_types(rt1,rt2))
+          return FunctionType(list(map(self.join_types, ps1, ps2)),
+                                 self.join_types(rt1,rt2))
         case (TupleType(ts1), TupleType(ts2)):
         case (TupleType(ts1), TupleType(ts2)):
-          return TupleType([self.join_types(ty1, ty2) for (ty1,ty2) in zip(ts1,ts2)])
+          return TupleType(list(map(self.join_types, ts1, ts2)))
         case (_, _):
         case (_, _):
           return t1
           return t1
         
         
@@ -19698,11 +19700,11 @@ cast from \INTTY{} to \CANYTY{} can be accomplished with the
 tagged value (Figure~\ref{fig:interp-Lany}). Similarly, a cast from
 tagged value (Figure~\ref{fig:interp-Lany}). Similarly, a cast from
 \CANYTY{} to \INTTY{} is accomplished with the \code{Project}
 \CANYTY{} to \INTTY{} is accomplished with the \code{Project}
 operator, that is, by checking the value's tag and either retrieving
 operator, that is, by checking the value's tag and either retrieving
-the underlying integer or signaling an error if it the tag is not the
+the underlying integer or signalling an error if the tag is not the
 one for integers (Figure~\ref{fig:interp-Lany-aux}).
 one for integers (Figure~\ref{fig:interp-Lany-aux}).
 %
 %
-Things get more interesting casts involving function, tuple, or array
-types, that is, casts involving higher-order types.
+Things get more interesting for casts involving function, tuple, or array
+types.
 
 
 Consider the cast of the function \code{maybe\_inc} from
 Consider the cast of the function \code{maybe\_inc} from
 \racket{\code{(Any -> Any)}}\python{\code{Callable[[Any], Any]}}
 \racket{\code{(Any -> Any)}}\python{\code{Callable[[Any], Any]}}
@@ -19713,8 +19715,8 @@ When the \code{maybe\_inc} function flows through
 this cast at runtime, we don't know whether it will return
 this cast at runtime, we don't know whether it will return
 an integer, as that depends on the input from the user.
 an integer, as that depends on the input from the user.
 The \LangCast{} interpreter therefore delays the checking
 The \LangCast{} interpreter therefore delays the checking
-of the cast until the function is applied. This is accomplished by
-wrapping \code{maybe\_inc} in a new function that casts its parameter
+of the cast until the function is applied. To do so it
+wraps \code{maybe\_inc} in a new function that casts its parameter
 from \INTTY{} to \CANYTY{}, applies \code{maybe\_inc}, and then
 from \INTTY{} to \CANYTY{}, applies \code{maybe\_inc}, and then
 casts the return value from \CANYTY{} to \INTTY{}.
 casts the return value from \CANYTY{} to \INTTY{}.
 
 
@@ -19808,7 +19810,7 @@ from \CANYTY{} to \INTTY{}.
   For the subscript \code{v[i]} in \code{f([v[i])} of \code{map\_inplace},
   For the subscript \code{v[i]} in \code{f([v[i])} of \code{map\_inplace},
   the proxy casts the integer from \INTTY{} to \CANYTY{}.
   the proxy casts the integer from \INTTY{} to \CANYTY{}.
   For the subscript on the left of the assignment,
   For the subscript on the left of the assignment,
-  the proxy casts the tagged value from from \CANYTY{} to \INTTY{}.
+  the proxy casts the tagged value from \CANYTY{} to \INTTY{}.
 }
 }
 
 
 The final category of cast that we need to consider are casts between
 The final category of cast that we need to consider are casts between
@@ -19941,7 +19943,7 @@ print( v[1] )
       case (FunctionType(ps1, rt1), FunctionType(ps2, rt2)):
       case (FunctionType(ps1, rt1), FunctionType(ps2, rt2)):
         params = [generate_name('x') for p in ps2]
         params = [generate_name('x') for p in ps2]
         args = [Cast(Name(x), t2, t1)
         args = [Cast(Name(x), t2, t1)
-                for (x,(t1,t2)) in zip(params, zip(ps1, ps2))]
+                for (x,t1,t2) in zip(params, ps1, ps2)]
         body = Cast(Call(ValueExp(value), args), rt1, rt2)
         body = Cast(Call(ValueExp(value), args), rt1, rt2)
         return Function('cast', params, [Return(body)], {})
         return Function('cast', params, [Return(body)], {})
       case (TupleType(ts1), TupleType(ts2)):
       case (TupleType(ts1), TupleType(ts2)):
@@ -20120,30 +20122,30 @@ The \code{cast\_insert} pass is closely related to the type checker
 for \LangGrad{} (starting in Figure~\ref{fig:type-check-Lgradual-1}).
 for \LangGrad{} (starting in Figure~\ref{fig:type-check-Lgradual-1}).
 In particular, the type checker allows implicit casts between
 In particular, the type checker allows implicit casts between
 consistent types. The job of the \code{cast\_insert} pass is to make
 consistent types. The job of the \code{cast\_insert} pass is to make
-those into explicit casts. This is accomplished by inserting
+those casts explicit. It does so by inserting
 \code{Cast} nodes into the AST.
 \code{Cast} nodes into the AST.
 %
 %
 For the most part, the implicit casts occur in places where the type
 For the most part, the implicit casts occur in places where the type
-checker checks two types for consistenty.  Consider the case for
+checker checks two types for consistency.  Consider the case for
 binary operators in Figure~\ref{fig:type-check-Lgradual-1}. The type
 binary operators in Figure~\ref{fig:type-check-Lgradual-1}. The type
 checker requires that the type of the left operand is consistent with
 checker requires that the type of the left operand is consistent with
 \INTTY{}. Thus, the \code{cast\_insert} pass should insert a
 \INTTY{}. Thus, the \code{cast\_insert} pass should insert a
 \code{Cast} around the left operand, converting from its type to
 \code{Cast} around the left operand, converting from its type to
-\INTTY{}. The story is similar for the right operand. Note that a cast
-is not always necessary, e.g., if the left operand already has type
-\INTTY{} then there is no need to insert a \code{Cast}.
+\INTTY{}. The story is similar for the right operand. It is not always
+necessary to insert a cast, e.g., if the left operand already has type
+\INTTY{} then there is no need for a \code{Cast}.
 
 
-Some of the implicit casts are not as straightforward, such as the
+Some of the implicit casts are not as straightforward. One such case
+arises with the
 conditional expression. In Figure~\ref{fig:type-check-Lgradual-1} we
 conditional expression. In Figure~\ref{fig:type-check-Lgradual-1} we
 see that the type checker requires that the two branches have
 see that the type checker requires that the two branches have
 consistent types and that type of the conditional expression is the
 consistent types and that type of the conditional expression is the
-join of the branches' types. In the target language \LangCast{}, the
-branches will need to have the same type as each other, and that type
-will be the type of the conditional expression. Thus, one must insert
-a \code{Cast} around each branch to convert from its type to the join
-type.
+join of the branches' types. In the target language \LangCast{}, both
+branches will need to have the same type, and that type
+will be the type of the conditional expression. Thus, each branch requires
+a \code{Cast} to convert from its type to the join type.
 
 
-The case for function call exhibits another interesting situation. If
+The case for the function call exhibits another interesting situation. If
 the function expression is of type \CANYTY{}, then it needs to be cast
 the function expression is of type \CANYTY{}, then it needs to be cast
 to a function type so that it can be used in a function call in
 to a function type so that it can be used in a function call in
 \LangCast{}. Which function type should it be cast to? The parameter
 \LangCast{}. Which function type should it be cast to? The parameter
@@ -20161,7 +20163,7 @@ to type \CANYTY{} (if they are not already of that type).
 The next step in the journey towards x86 is the \code{lower\_casts}
 The next step in the journey towards x86 is the \code{lower\_casts}
 pass that translates the casts in \LangCast{} to the lower-level
 pass that translates the casts in \LangCast{} to the lower-level
 \code{Inject} and \code{Project} operators and new operators for
 \code{Inject} and \code{Project} operators and new operators for
-proxies, extending the \LangLam{} language to create \LangProxy{}.
+proxies, extending the \LangLam{} language to \LangProxy{}.
 The \LangProxy{} language can also be described as an extension of
 The \LangProxy{} language can also be described as an extension of
 \LangAny{}, with the addition of proxies. We recommend creating an
 \LangAny{}, with the addition of proxies. We recommend creating an
 auxiliary function named \code{lower\_cast} that takes an expression
 auxiliary function named \code{lower\_cast} that takes an expression
@@ -20173,7 +20175,7 @@ the \code{apply\_cast} function (Figure~\ref{fig:apply_cast}) used in
 the interpreter for \LangCast{} because it must handle the same cases
 the interpreter for \LangCast{} because it must handle the same cases
 as \code{apply\_cast} and it needs to mimic the behavior of
 as \code{apply\_cast} and it needs to mimic the behavior of
 \code{apply\_cast}. The most interesting cases are those concerning
 \code{apply\_cast}. The most interesting cases are those concerning
-the casts involing tuple, array, and function types.
+the casts involving tuple, array, and function types.
 
 
 As mentioned in Section~\ref{sec:interp-casts}, a cast from one array
 As mentioned in Section~\ref{sec:interp-casts}, a cast from one array
 type to another array type is accomplished by creating a proxy that
 type to another array type is accomplished by creating a proxy that
@@ -20337,7 +20339,7 @@ Likewise, we return the
 meaning, as the type of arrays, and we introduce a new type,
 meaning, as the type of arrays, and we introduce a new type,
 \PARRAYTYNAME{}, whose values
 \PARRAYTYNAME{}, whose values
 can be either arrays or array proxies.
 can be either arrays or array proxies.
-These new types come with a suite of new primitive operations
+These new types come with a suite of new primitive operations.
 
 
 {\if\edition\racketEd    
 {\if\edition\racketEd    
 A tuple proxy is represented by a tuple containing three things: 1) the
 A tuple proxy is represented by a tuple containing three things: 1) the
@@ -20465,12 +20467,12 @@ and primitive functions.
 
 
 \fi}
 \fi}
 
 
-Now to discuss the translation that differentiates tuples and arrays
+Now we discuss the translation that differentiates tuples and arrays
 from proxies. First, every type annotation in the program is
 from proxies. First, every type annotation in the program is
 translated (recursively) to replace \TUPLETYPENAME{} with \PTUPLETYNAME{}.
 translated (recursively) to replace \TUPLETYPENAME{} with \PTUPLETYNAME{}.
 Next, we insert uses of \PTUPLETYNAME{} operations in the appropriate
 Next, we insert uses of \PTUPLETYNAME{} operations in the appropriate
 places. For example, we wrap every tuple creation with an
 places. For example, we wrap every tuple creation with an
-\racket{\code{inject-vector}}\python{\code{InjectTupleProxy}}.
+\racket{\code{inject-vector}}\python{\code{InjectTuple}}.
 {\if\edition\racketEd    
 {\if\edition\racketEd    
 \begin{lstlisting}
 \begin{lstlisting}
 (vector |$e_1 \ldots e_n$|)
 (vector |$e_1 \ldots e_n$|)
@@ -20535,7 +20537,7 @@ operation.
 \fi}
 \fi}
 %
 %
 Note that in the branch for a tuple, we must apply
 Note that in the branch for a tuple, we must apply
-\racket{\code{project-vector}} \python{project\_tuple} before reading
+\racket{\code{project-vector}}\python{\code{project\_tuple}} before reading
 from the tuple.
 from the tuple.
 
 
 The translation of array operations is similar to the ones for tuples.
 The translation of array operations is similar to the ones for tuples.
@@ -20612,7 +20614,7 @@ Assign([|$\itm{lhs}$|], InjectTuple(|$e_1$|))
 movq |$e'_1$|, |$\itm{lhs'}$|
 movq |$e'_1$|, |$\itm{lhs'}$|
 \end{lstlisting}
 \end{lstlisting}
 \fi}
 \fi}
-\python{The translation for \code{InjectList} is just a move instruction.}
+\python{The translation for \code{InjectList} is also a move instruction.}
 \noindent On the other hand,
 \noindent On the other hand,
 \racket{\code{inject-proxy}}\python{\code{InjectTupleProxy}} sets bit
 \racket{\code{inject-proxy}}\python{\code{InjectTupleProxy}} sets bit
 $63$ to $1$.
 $63$ to $1$.
@@ -20675,7 +20677,7 @@ movq %rax, |$\itm{lhs'}$|
 %
 %
 The \racket{\code{project-vector} operation is}
 The \racket{\code{project-vector} operation is}
 \python{\code{project\_tuple} and \code{project\_array} operations are}
 \python{\code{project\_tuple} and \code{project\_array} operations are}
-straightforward to translate, so we leave that up to the reader.
+straightforward to translate, so we leave that to the reader.
 
 
 Regarding the element access operations for tuples and arrays, the
 Regarding the element access operations for tuples and arrays, the
 runtime provides procedures that implement them (they are recursive
 runtime provides procedures that implement them (they are recursive
@@ -20769,7 +20771,7 @@ and \code{proxy\_vec\_length} functions.
   extending and adapting your compiler for \LangLam{}. Create 10 new
   extending and adapting your compiler for \LangLam{}. Create 10 new
   partially-typed test programs. In addition to testing with these
   partially-typed test programs. In addition to testing with these
   new programs, also test your compiler on all the tests for \LangLam{}
   new programs, also test your compiler on all the tests for \LangLam{}
-  and tests for \LangDyn{}.
+  and for \LangDyn{}.
 %
 %
   \racket{Sometimes you may get a type checking error on the
   \racket{Sometimes you may get a type checking error on the
     \LangDyn{} programs but you can adapt them by inserting a cast to
     \LangDyn{} programs but you can adapt them by inserting a cast to
@@ -20879,7 +20881,7 @@ original location of the cast in the source program.
 
 
 The problem addressed by space-efficient casts also relates to
 The problem addressed by space-efficient casts also relates to
 higher-order casts. It turns out that in partially typed programs, a
 higher-order casts. It turns out that in partially typed programs, a
-function or tuple can flow through very-many casts at runtime. With
+function or tuple can flow through very many casts at runtime. With
 the approach described in this chapter, each cast adds another
 the approach described in this chapter, each cast adds another
 \code{lambda} wrapper or a tuple proxy. Not only does this take up
 \code{lambda} wrapper or a tuple proxy. Not only does this take up
 considerable space, but it also makes the function calls and tuple
 considerable space, but it also makes the function calls and tuple
@@ -20890,7 +20892,7 @@ algorithm from $O(n^2)$ to $O(n^3)$! \citet{Herman:2006uq} suggested a
 solution to this problem by representing casts using the coercion
 solution to this problem by representing casts using the coercion
 calculus of \citet{Henglein:1994nz}, which prevents the creation of
 calculus of \citet{Henglein:1994nz}, which prevents the creation of
 long chains of proxies by compressing them into a concise normal
 long chains of proxies by compressing them into a concise normal
-form. \citet{Siek:2015ab} give and algorithm for compressing coercions
+form. \citet{Siek:2015ab} give an algorithm for compressing coercions
 and \citet{Kuhlenschmidt:2019aa} show how to implement these ideas in
 and \citet{Kuhlenschmidt:2019aa} show how to implement these ideas in
 the Grift compiler.
 the Grift compiler.
 \begin{center}
 \begin{center}
@@ -20900,7 +20902,8 @@ the Grift compiler.
 There are also interesting interactions between gradual typing and
 There are also interesting interactions between gradual typing and
 other language features, such as parametetric polymorphism,
 other language features, such as parametetric polymorphism,
 information-flow types, and type inference, to name a few. We
 information-flow types, and type inference, to name a few. We
-recommend the reader to the online gradual typing bibliography:
+recommend the reader to consult the online gradual typing bibliography
+for more material:
 \begin{center}
 \begin{center}
   \url{http://samth.github.io/gradual-typing-bib/}
   \url{http://samth.github.io/gradual-typing-bib/}
 \end{center}
 \end{center}