From 0ef8bfb3f9ec52beba79dec8093ffc4c330a557e Mon Sep 17 00:00:00 2001 From: Adam Chlipala Date: Sun, 17 Jul 2011 10:27:09 -0400 Subject: Tutorial: Names and Records --- doc/tlc.ur | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 doc/tlc.ur diff --git a/doc/tlc.ur b/doc/tlc.ur new file mode 100644 index 00000000..22eb1e1f --- /dev/null +++ b/doc/tlc.ur @@ -0,0 +1,35 @@ +(* Chapter 2: Type-Level Computation *) + +(* This tutorial by Adam Chlipala is licensed under a Creative Commons Attribution-Noncommercial-No Derivative Works 3.0 Unported License. *) + +(* The last chapter reviewed some Ur features imported from ML and Haskell. This chapter explores uncharted territory, introducing the features that make Ur unique. *) + +(* * Names and Records *) + +(* Last chapter, we met Ur's basic record features, including record construction and field projection. *) + +val r = { A = 0, B = 1.2, C = "hi"} + +(* begin eval *) +r.B +(* end *) + +(* Our first taste of Ur's novel expressive power is with the following function, which implements record field projection in a completely generic way. *) + +fun project [nm :: Name] [t ::: Type] [ts ::: {Type}] [[nm] ~ ts] (r : $([nm = t] ++ ts)) : t = r.nm + +(* begin eval *) +project [#B] r +(* end *) + +(* This function introduces a slew of essential features. First, we see type parameters with explicit kind annotations. Formal parameter syntax like [a :: K] declares an explicit parameter a of kind K. Explicit parameters must be passed explicitly at call sites. In contrast, implicit parameters, declared like [a ::: K], are inferred in the usual way.
+
+Two new kinds appear in this example. We met the basic kind Type in a previous example. Here we meet Name, the kind of record field names; and {Type} the type of finite maps from field names to types, where we'll generally refer to this notion of "finite map" by the name record, as it will be clear from context whether we're discussing type-level or value-level records. That is, in this case, we are referring to names and records at the level of types that exist only at compile time! By the way, the kind {Type} is one example of the general {K} kind form, which refers to records with fields of kind K.
+
+The English description of project is that it projects a field with name nm and type t out of a record r whose other fields are described by type-level record ts. We make all this formal by assigning r a type that first builds the singleton record [nm = t] that maps nm to t, and then concatenating this record with the remaining field information in ts. The $ operator translates a type-level record (of kind {Type}) into a record type (of kind Type).
+
+The type annotation on r uses the record concatenation operator ++. Ur enforces that any concatenation happens between records that share no field names. Otherwise, we'd need to resolve field name ambiguity in some predictable way, which would force us to treat ++ as non-commutative, if we are to maintain the nice modularity properties of polymorphism. However, treating ++ as commutative, and treating records as equal up to field permutation in general, are very convenient for type inference and general programmer experience. Thus, we enforce disjointness to keep things simple.
+
+For a polymorphic function like project, the compiler doesn't know which fields a type-level record variable like ts contains. To enable self-contained type-checking, we need to declare some constraints about field disjointness. That's exactly the meaning of syntax like [r1 ~ r2], which asserts disjointness of two type-level records. The disjointness clause for project asserts that the name nm is not used by ts. The syntax [nm] is shorthand for [nm = ()], which defines a singleton record of kind {Unit}, where Unit is the degenerate kind inhabited only by the constructor ().
+
+The last piece of this puzzle is the easiest. In the example call to project, we see that the only parameters passed are the one explicit constructor parameter nm and the value-level parameter r. The rest are inferred, and the disjointness proof obligation is discharged automatically. The syntax #A denotes the constructor standing for first-class field name A, and we pass all constructor parameters to value-level functions within square brackets (which bear no formal relation to the syntax for type-level record literals [A = c, ..., A = c]). *) -- cgit v1.2.3