l e t print_name r = p r i n t _ e n d l i n e ( Name : ^ r. name)

Similar documents
Lecture #23: Conversion and Type Inference

Conversion vs. Subtyping. Lecture #23: Conversion and Type Inference. Integer Conversions. Conversions: Implicit vs. Explicit. Object x = "Hello";

Lecture #13: Type Inference and Unification. Typing In the Language ML. Type Inference. Doing Type Inference

Tracing Ambiguity in GADT Type Inference

CS558 Programming Languages

Goal. CS152: Programming Languages. Lecture 15 Parametric Polymorphism. What the Library Likes. What The Client Likes. Start simpler.

Programming Languages and Compilers (CS 421)

CS558 Programming Languages

CSCI-GA Scripting Languages

Higher-Order Intensional Type Analysis. Stephanie Weirich Cornell University

Types, Type Inference and Unification

Type structure is a syntactic discipline for maintaining levels of abstraction John Reynolds, Types, Abstraction and Parametric Polymorphism

CS-XXX: Graduate Programming Languages. Lecture 17 Recursive Types. Dan Grossman 2012

COSE212: Programming Languages. Lecture 3 Functional Programming in OCaml

CSE-505: Programming Languages. Lecture 18 Existential Types. Zach Tatlock 2016

CSE-505: Programming Languages. Lecture 20.5 Recursive Types. Zach Tatlock 2016

Polymorphic lambda calculus Princ. of Progr. Languages (and Extended ) The University of Birmingham. c Uday Reddy

CMSC 330: Organization of Programming Languages

Type Checking and Type Inference

ML Type Inference and Unification. Arlen Cox

Adding GADTs to OCaml the direct approach

TYPE INFERENCE. François Pottier. The Programming Languages Mentoring ICFP August 30, 2015

Introduction to Haskell

Parametricity. Leo White. February Jane Street 1/ 70

Type Systems, Type Inference, and Polymorphism

QUIZ. What is wrong with this code that uses default arguments?

Generic polymorphism on steroids

CS558 Programming Languages

ML 4 Unification Algorithm

Lists. Michael P. Fourman. February 2, 2010

Types and Type Inference

Harvard School of Engineering and Applied Sciences Computer Science 152

GADTs meet Subtyping

CS 11 Ocaml track: lecture 2

RSL Reference Manual

Types and Type Inference

CMSC 330: Organization of Programming Languages. OCaml Expressions and Functions

Lecture 12: Data Types (and Some Leftover ML)

Topics Covered Thus Far. CMSC 330: Organization of Programming Languages. Language Features Covered Thus Far. Programming Languages Revisited

Abstraction. Leo White. February Jane Street 1/ 88

GADTs. Wouter Swierstra. Advanced functional programming - Lecture 7. Faculty of Science Information and Computing Sciences

Products and Records

Arbitrary-rank polymorphism in (GHC) Haskell

Programming with Types

CS152: Programming Languages. Lecture 11 STLC Extensions and Related Topics. Dan Grossman Spring 2011

If a program is well-typed, then a type can be inferred. For example, consider the program

(Refer Slide Time: 4:00)

Part VI. Imperative Functional Programming

First-Class Type Classes

Types-2. Polymorphism

A Type is a Set of Values. Here we declare n to be a variable of type int; what we mean, n can take on any value from the set of all integer values.

OCaml Data CMSC 330: Organization of Programming Languages. User Defined Types. Variation: Shapes in Java

Type Systems. Seman&cs. CMPT 379: Compilers Instructor: Anoop Sarkar. anoopsarkar.github.io/compilers-class

CS 11 Haskell track: lecture 1

Flang typechecker Due: February 27, 2015

Unifying Nominal and Structural Ad-Hoc Polymorphism

Modular implicits for OCaml how to assert success. Gallium Seminar,

Semantic Processing (Part 2)

Typed Racket: Racket with Static Types

Programming Languages and Techniques (CIS120)

Programming Languages Lecture 15: Recursive Types & Subtyping

Hard deadline: 3/28/15 1:00pm. Using software development tools like source control. Understanding the environment model and type inference.

Type-indexed functions in Generic Haskell

Tradeoffs. CSE 505: Programming Languages. Lecture 15 Subtyping. Where shall we add useful completeness? Where shall we add completeness?

INF 212 ANALYSIS OF PROG. LANGS Type Systems. Instructors: Crista Lopes Copyright Instructors.

Pattern Matching and Abstract Data Types

GADTs. Wouter Swierstra and Alejandro Serrano. Advanced functional programming - Lecture 7. [Faculty of Science Information and Computing Sciences]

Programming Languages Assignment #7

Functions & First Class Function Values

Polymorphism and Type Inference

Agenda. CS301 Session 11. Common type constructors. Things we could add to Impcore. Discussion: midterm exam - take-home or inclass?

CS153: Compilers Lecture 15: Local Optimization

Some instance messages and methods

GADTs. Alejandro Serrano. AFP Summer School. [Faculty of Science Information and Computing Sciences]

Introduction to Typed Racket. The plan: Racket Crash Course Typed Racket and PL Racket Differences with the text Some PL Racket Examples

A quick introduction to SML

Polymorphism. Chapter Eight Modern Programming Languages, 2nd ed. 1

Inductive Data Types

Static Checking and Type Systems

CSC324- TUTORIAL 5. Shems Saleh* *Some slides inspired by/based on Afsaneh Fazly s slides

Better typing errors for OCaml. Arthur Charguéraud Inria

Introduction to OCaml

CS153: Compilers Lecture 14: Type Checking

INF 212/CS 253 Type Systems. Instructors: Harry Xu Crista Lopes

Topic 16: Issues in compiling functional and object-oriented languages

1 Introduction. 3 Syntax

Haskell Introduction Lists Other Structures Data Structures. Haskell Introduction. Mark Snyder

Mini-ML. CS 502 Lecture 2 8/28/08

CSE 130: Programming Languages. Polymorphism. Ranjit Jhala UC San Diego

int f(char a, char b) {

COS 320. Compiling Techniques

Once Upon a Polymorphic Type

CSE 505, Fall 2008, Final Examination 11 December Please do not turn the page until everyone is ready.

Lambda Calculus and Type Inference

Haskell 98 in short! CPSC 449 Principles of Programming Languages

Dialects of ML. CMSC 330: Organization of Programming Languages. Dialects of ML (cont.) Features of ML. Functional Languages. Features of ML (cont.

Programming Languages and Techniques (CIS120)

CSE 505: Concepts of Programming Languages

(Refer Slide Time: 01:01)

Topic 9: Type Checking

Transcription:

Chapter 8 Row polymorphism Consider the following code: type name_home = {name : s t r i n g ; home : s t r i n g } type name_mobile = {name : s t r i n g ; mobile : s t r i n g } l e t jane = {name = Jane ; home = 01234 654321 } l e t john = {name = John ; mobile = 07654 123456 } l e t print_name r = p r i n t _ e n d l i n e ( Name : ^ r. name) l e t ( ) = print_name jane ; print_name john We create two records, one for jane and one for john, and then we print the name fields of these records. These all seems pretty reasonable, but it doesn t work: print_name jane; ^^^^ Error: This expression has type name_home but an expression was expected of type name_mobile The problem is that jane and john have different record field labels. Whilst both records have fields labelled name, jane also has a field labelled home whereas john has a field labelled mobile. Since they have different fields, jane and john have different types, but print_name only works on a single type (in this case name_mobile) hence the error message. Since print_name only uses the name field, we would like to be able to use it with any type that has such a field. In other words, we would like the type of print_name to be polymorphic in such a way that it can be instantiated to any type with a name field. 1

2 CHAPTER 8. ROW POLYMORPHISM More generally, we would like all operations on records to have polymorphic types, which can be instantiated to any record type which meets the requirements of that operation. There are three basic operations on records, from which we can build up any more complex operations: 1. An empty record (empty) 2. Extend a record with a field (extend) 3. Access the contents of a field (access) 8.1 Presence variables 8.1.1 Presence and absence For a finite set of field labels we can define polymorphic record types by representing an absent field using a nullary type constructor absent and a present field using a unary type constructor present whose argument is the type of the field. For example, records with fields labelled either name, home or mobile can be encoded as: type absent = Absent type ' a p r e s e n t = Present : ' a > ' a p r e s e n t type ( ' name, ' home, ' mobile ) r e cord = { name : ' name ; home : ' home ; mobile : ' mobile ; } The basic operations on these records are defined as: l e t empty = { name = Absent ; home = Absent ; m o b i l e = Absent } l e t extend name { home ; mobile ; _ } = { name = Present name ; home ; mobile } l e t extend home { name ; mobile ; _ } = { name ; home = Present home ; mobile } l e t extend mobile { name ; home ; _ } = { name ; home ; mobile = Present mobile } l e t a c c e s s { name = Present name ; _ } = name l e t a c c e s s { home = Present home ; _ } = home

8.1. PRESENCE VARIABLES 3 l e t a c c e s s { mobile = Present mobile ; _ } = mobile which have types: val empty : ( absent, absent, absent ) r ecord val extend : ' d > ( ' a, ' b, ' c ) r ecord > ( ' d present, ' b, ' c ) r ecord val extend : ' d > ( ' a, ' b, ' c ) record > ( ' a, ' d present, ' c ) r ecord val extend : ' d > ( ' a, ' b, ' c ) record > ( ' a, ' b, ' d p r e s e n t ) record val a c c e s s : ( ' a present, ' b, ' c ) record > ' a val a c c e s s : ( ' a, ' b present, ' c ) r ecord > ' b val a c c e s s : ( ' a, ' b, ' c p r e s e n t ) r ecord > ' c As you can see, the functions for extending and accessing records are now polymorphic in the presence of the fields that they do not operate on. Using this scheme, we can encode our print_name example as follows: l e t jane = extend Jane ( extend 01234 654321 empty ) l e t john = extend John ( extend 07654 123456 empty ) l e t print_name r = p r i n t _ e n d l i n e ( Name : ^ ( a c c e s s r ) ) l e t ( ) = print_name jane ; print_name john 8.1.2 Polymorphic record extension It is clear that field access should only work on records for which the field is present. However, should record extension always return a record where the field is present? Consider the following code: l e t person p = i f p then jane e l s e john

4 CHAPTER 8. ROW POLYMORPHISM With our original encoding this does not type-check because jane and john do not have the same set of fields. However, it should be safe to give person the type bool -> (string present, absent, absent) record because both jane and john are records with a name field of type string. In general, it should always be safe to treat a record as if it had fewer fields than it actually has. Whilst it is safe to treat a record with a home field of type string as if it did not have a home field, it is not safe to treat it as if the home field had type int. So record extension can be polymorphic in the presence of a field but not in the type of a field. This means that we would like the definition of record extension to use higher-kinded polymorphism. Since higher-kinded polymorphism in OCaml requires the module system which is quite verbose we will express the type of record extension using System Fω.: extend : α :. β :. γ :. δ :. φ :. δ Record α β γ Record (φ δ ) β γ where φ is a type constructor variable that can be instantiated with either Present or (λα:. Absent). We call such higher-kinded type variables presence variables. 8.1.3 Ensuring record types are well-formed Polymorphic record types using presence variables allow some type expressions that don t make sense by instantiating the presence variables with type constructors other than Present or Absent (e.g. Record Int (Present String) (Present String)) or by using Present or Absent outside of a record type (e.g. List (Present Int)). This is still type-safe because all the operations on record types only work on well-formed record types, however it is undesirable to allow ill-formed types to even be expressed. Such ill-formed types are usually prevented using a kind system by creating a new kind presence such that: Absent : presence Present : p r e s e n c e Record : presence presence p r e s e n c e 8.2 Row variables In the previous section we were able to encode polymorphic records for a fixed finite set of field labels. This requires us to know in advance all the field labels that will be used with our polymorphic records, which prevents separate compilation. What we really want is to be able to encode polymorphic records for an infinite set of field labels (e.g. all possible strings of alphanumeric characters). We will write L to represent some infinite set of labels. If we assume the existence of infinite record types, containing one field for each of the field labels, then we can use the same encoding as in the previous

8.2. ROW VARIABLES 5 section to encode polymorphic records. We will write { ; foo: bar; } to represent such a record type with the foo field having type bar, and we will use l and m to represent generic labels. empty : {... ; l : Absent ;... } extend : α :. β : p r e s e n c e..... γ : p r e s e n c e.... φ : p r e s e n c e. α {... ; m : β ;... ; l : γ ;... } {... ; m : φ α ;... ; l : γ ;... } a c c e s s : α :..... β : p r e s e n c e.... {... ; m : Present α ;... ; l : β ;... } α Unfortunately, we cannot use such infinite record types directly, because they have an infinite number of type parameters. This means, for example, that unification of two of these types will not terminate. However, the type schemes for empty, extend_m and access_m above only use infinite record types of a particular form. Each record type appearing above can be divided into two parts: 1. A finite part 2. A co-finite part where either every type parameter is a free variable or every type parameter is Absent. The infinite record types in the above definitions are divided as follows: Record type Finite part Co-finite part {... ; l : Absent ;...} { } {... ; l : Absent ;...} {... ; m : β ;... ; l : γ ;...} {m : β} {... ; l : γ ;...} {... ; m : φ α ;... ; l : γ ;...} {m : φ α} {... ; l : γ ;...} {... ; m : Present α ;... ; l : β ;...} { m : Present α } {... ; l : β ;...} By splitting our infinite record types up like this we can give them a finite representation. If the co-finite part has every type parameter equal to absent then we represent the record type by just its finite part. If the co-finite part has type variables for every parameter then we represent the record type as a pair of the finite part and a single type variable to represent the co-finite part. For convenience we write this pair as { finite-part ρ }. The type variables ρ representing co-finite records are called row variables. As with presence variables, the kind system is usually used to ensure that record types are well formed. A row variable reprsenting a co-finite record without labels m to m is given kind row(m,..., m ). This prevents types which unify row variables with regular types (e.g. { m : Present Float Int }), or record types including multiple occurrences of the same label (e.g. { m : Present Float { m: Present Int } }). Using this finite representation, our operations have the following type schemes:

6 CHAPTER 8. ROW POLYMORPHISM empty : {} extend : α :. β : p r e s e n c e. ρ : row (m). φ : p r e s e n c e. α {m : β ρ} {m : φ α ρ} a c c e s s : α :. ρ : row (m). {m : Present α ρ} α With this finite representation we can perform unification using variations of the standard ML algorithms. These algorithms maintain all the good properties that ML type inference has, in particular they still have principal types. Infinite record types which can be represented in this way are closed under unification, which means that we never have to handle a record type which is not of this form. 8.3 Polymorphic variants Consider the following code: type number = Int : i n t > number Float : f l o a t > number l e t square = f u n c t i o n Int i > Int ( i * i ) Float f > Float ( f *. f ) type constant = Int : i n t > constant Float : f l o a t > constant S t r i n g : s t r i n g > constant l e t print_constant = f u n c t i o n Int i > p r i n t _ i n t i Float f > p r i n t _ f l o a t f S t r i n g s > p r i n t _ s t r i n g s l e t ( ) = print_constant ( square ( Int 5 ) ) We define a function square which squares a number that is either an int or a float, and a function print_constant which prints something that is either an int, a float or a string. We then use these functions to square 5 and print the result. This code seems reasonable, but will not type check. Characters 24-40:

8.3. POLYMORPHIC VARIANTS 7 let () = print_constant (square (Int 5));; ^^^^^^^^^^^^^^^^ Error: This expression has type number but an expression was expected of type constant The square function and the print_constant function work on different variant constructor labels. Whilst they both work on constructors labelled Int and Float, print_constant also works on constructors labelled String. Since they work on different constructors, square and print_constant work on different types, hence the error. You may notice some similarity between the above example and jane and john example from the beginning of this chapter. This comes from the duality between records and variants. This duality implies that we can also use row variables and presence variables to support this example. As with records, what we would like is for all operations on variants to have polymorphic types, which can be instantiated to any variant type which meets the requirements of that operation. The three basic operations on variants, dual to the three basic operations on records, are as follows: 1. Match a variant with no constructors (match_empty) 2. Extend a match with a variant constructor (extend_match) 3. Use a variant constructor (create) Using these operations, we can write square and print_constant as follows: l e t square = extend_match ( fun i > c r e a t e ( i * i ) ) ( extend_match ( fun f > c r e a t e ( f *. f ) ) match_empty ) l e t print_constant = extend_match ( fun i > p r i n t _ i n t i ) ( extend_match ( fun f > p r i n t _ f l o a t f ) ( extend_match ( fun s > p r i n t _ s t r i n g s ) match_empty ) ) l e t ( ) = print_constant ( square ( c r e a t e 5 ) ) To distinguish polymorphic variant types from polymorphic record types we will write them using the syntax [Foo : bar ρ] where Foo is a constructor label, bar is the type of the constructor labelled Foo and ρ is the (optional) row variable. Using row variables and presence variables, we can give the basic operations on variants the following types: match_empty : α :. [ ] α

8 CHAPTER 8. ROW POLYMORPHISM extend_match : α :. β : p r e s e n c e. γ :. ρ : row (M). φ : presence. (α γ ) ( [M : β ρ ] γ ) [M : φ α ρ ] γ c r e a t e : α :. ρ : row (M). α [M : Present α ρ ] 8.4 Row polymorphism in OCaml Regular OCaml records and variants are not polymorphic. There are two good reasons for this: 1. Monomorphic records and variants can be implemented more efficiently than their polymorphic counterparts. 2. Polymorphic records and variants are more flexible, but this additional flexibility makes types more complicated and type errors more difficult to understand. However, OCaml does also provide objects (similar to polymorphic records) and polymorphic variants. Both of these are typed using forms of row polymorphism. In both cases the form of row polymorphism is more restrictive the row polymorphism described in this chapter, and this section discusses the limitations of these restricted forms. 8.4.1 OCaml s objects The jane and john example from the beginning of the chapter can be implemented using OCaml objects: l e t jane = o b j e c t method name = Jane method home = 01234 654321 end l e t john = o b j e c t method name = John method mobile = 07654 123456 end l e t print_name r = p r i n t _ e n d l i n e ( Name : ^ r#name) l e t ( ) = print_name jane ; print_name john

8.4. ROW POLYMORPHISM IN OCAML 9 The OCaml object type syntax comes in two forms: < foo : int; bar : float > represents an object type where the method foo has type int and the method bar has type float. Both methods are present, and all other methods are absent. < foo: int; bar : float;.. > is the same as above, except the object may contain other methods besides foo and bar. In other words, the.. represents an unnamed row variable. Using this syntax, the type schemes of jane, john and print_name are as follows: val jane : < home : s t r i n g ; name : s t r i n g > val john : < mobile : s t r i n g ; name : s t r i n g > val print_name : < name : s t r i n g ;.. > > unit 8.4.2 OCaml s Polymorphic variants Using OCaml s polymorphic variants we can write the square example: l e t square = f u n c t i o n ` Int i > ` Int ( i * i ) ` Float f > ` Float ( f *. f ) l e t print_constant = f u n c t i o n ` Int i > print_int i ` Float f > p r i n t _ f l o a t f ` S t r i n g s > p r i n t _ s t r i n g s l e t ( ) = print_constant ( square ( ` Int 5 ) ) The OCaml polymorphic variant type syntax is a bit complicated and comes in four forms: [ `Foo of int `Bar of float ] represents a variant type where the constructor `Foo has type int and the constructor `Bar has type float. Both constructors are definitely present. [< `Foo of int `Bar of float ] is the same as above, except that it is polymorphic in the presence of both constructors. In other words, the < represents two unnamed presence variables. [< `Foo of int `Bar of float > `Bar ] is the same as above, except that it is only polymorphic in the presence of the `Foo constructor the `Bar constructor is definitely present. In other words, the < represents a single unnamed presence variable associated with `Foo.

10 CHAPTER 8. ROW POLYMORPHISM [> `Foo of int `Bar of float] is the same as the first form, except their may be more constructors than just `Foo and `Bar. In other words, the > represents an unnamed row variable. Using this syntax, the type schemes of the square and print_constant functions are as follows: val square : [< ` Float o f f l o a t ` Int o f i n t ] > [> ` Float o f f l o a t ` Int o f i n t ] val print_constant : [< ` Float o f f l o a t ` Int o f i n t ` S t r i n g o f s t r i n g ] > unit 8.4.3 Limitations Hidden row variables Both object types and polymorphic variant types in OCaml hide the row variables. This simplifies the types and avoids the need to expose the user to type variables of kinds other than *. However, it also places some limitations on how objects and polymorphic records can be used relative to the more explicit row polymorphism described in the previous sections. Not allowing row variables to be named means that row variables cannot be shared between different object types. Looking at the types of the basic operations on records, we can see that this means we cannot express the type of polymorphic record extension (extend ). Instead of supporting polymorphic record extension, OCaml objects can only be created monomorphically using object literals. This is equivalent to providing a family of operations of the form: val c r e a t e : ' a > ' b > ' c > < l : ' a ; m : ' b ; n : ' c > instead of empty and extend. In practical terms, this means that we cannot take a generic object and extend it with a new method. We can only create a new record if we statically know all of the its methods. As with object types, row variables cannot be shared between different variant types. Looking at the types of the basic operations on variants, we can see that this means we cannot express the type of polymorphic match extension (extend_match ). Instead of supporting polymorphic match extension, polymorphic variants can only be matched using a single match statement. This is equivalent to providing a family of operations of the form: val match : ( ' a > ' d ) > ( ' b > ' d ) > ( ' c > ' d ) > [< `L o f ' a `M o f ' b `N o f ' c ] > ' d

8.4. ROW POLYMORPHISM IN OCAML 11 instead of match_empty and extend_match. In practical terms, this means we cannot take a generic function on polymorphic variants and extend it with a new case for a variant constructor. Objects and presence variables OCaml s objects (unlike OCaml s polymorphic variants) do not support presence variables. This simplifies the types for objects, but means that the person example cannot be type-checked using OCaml objects: l e t person p = i f p then jane e l s e john Characters 35-39: let person p = if p then jane else john ^^^^ Error: This expression has type < mobile : string; name : string > but an expression was expected of type < home : string; name : string > The second object type has no method mobile However, separately from row polymorphism, OCaml does provide an explicit subtyping coercion operator :>, which can be used in this case: # l e t person p = i f p then ( jane :> < name : s t r i n g >) e l s e ( john :> < name : s t r i n g >);; val person : bool > < name : s t r i n g > = <fun> Subtyping is a related but separate concept from row polymorphism. For a good overview of subtyping see Part III of Types and Programming Languages by Pierce.