Reconciling OO and Haskell-Style Overloading Mark Shields Simon Peyton Jones Microsoft Research Cambridge

Slides:



Advertisements
Similar presentations
Types and Programming Languages Lecture 13 Simon Gay Department of Computing Science University of Glasgow 2006/07.
Advertisements

Functional Programming Lecture 10 - type checking.
Brown Bag #3 Return of the C++. Topics  Common C++ “Gotchas”  Polymorphism  Best Practices  Useful Titbits.
Extended Static Checking for Haskell (ESC/Haskell) Dana N. Xu University of Cambridge advised by Simon Peyton Jones Microsoft Research, Cambridge.
CSE 332: C++ copy control I Copy Control (Part I) Copy control consists of 5 distinct operations –A copy constructor initializes an object by duplicating.
C++ Training Datascope Lawrence D’Antonio Lecture 6 An Overview of C++: What is Polymorphism? - Coercion.
CSE 425: Semantics II Implementing Scopes A symbol table is in essence a dictionary –I.e., every name appears in it, with the info known about it –Usually.
Modular Type Classes Derek Dreyer Robert Harper Manuel M.T. Chakravarty POPL 2007 Nice, France.
Road Map Introduction to object oriented programming. Classes
13. Summary, Trends, Research. © O. Nierstrasz PS — Summary, Trends, Research Summary, Trends, Research...  Summary: functional, logic and object-oriented.
Catriel Beeri Pls/Winter 2004/5 type reconstruction 1 Type Reconstruction & Parametric Polymorphism  Introduction  Unification and type reconstruction.
Generative type abstraction and type-level computation (Wrestling with System FC) Stephanie Weirich, Steve Zdancewic University of Pennsylvania Dimitrios.
Scrap your boilerplate with class Ralf Lämmel, Simon Peyton Jones Microsoft Research.
Terms and Rules Professor Evan Korth New York University (All rights reserved)
1 Type Type system for a programming language = –set of types AND – rules that specify how a typed program is allowed to behave Why? –to generate better.
Using Types Slides thanks to Mark Jones. 2 Expressions Have Types: The type of an expression tells you what kind of value you might expect to see if you.
Classes. Class expanded concept of a data structure: instead of holding only data, it can hold both data and functions. An object is an instantiation.
CSE 332: C++ Overloading Overview of C++ Overloading Overloading occurs when the same operator or function name is used with different signatures Both.
Type Classes with Functional Dependencies Mark P Jones, Oregon Graduate Institute The theory of relational databases meets.
Comparison of OO Programming Languages © Jason Voegele, 2003.
Abstract classes and Interfaces. Abstract classes.
Chapter 12: Adding Functionality to Your Classes.
C++ Object Oriented 1. Class and Object The main purpose of C++ programming is to add object orientation to the C programming language and classes are.
Copyright © 2011 Pearson Education, Inc. Publishing as Pearson Addison-Wesley Taken from slides of Starting Out with C++ Early Objects Seventh Edition.
CSE 332: C++ templates This Week C++ Templates –Another form of polymorphism (interface based) –Let you plug different types into reusable code Assigned.
MrFlow: Why MrSpidey Failed Philippe Meunier Paul Steckler.
 2006 Pearson Education, Inc. All rights reserved Classes: A Deeper Look, Part 2.
Inheritance Extending Class Functionality. Polymorphism Review Earlier in the course, we examined the topic of polymorphism. Many times in coding, we.
Copyright 2003 Scott/Jones Publishing Standard Version of Starting Out with C++, 4th Edition Chapter 13 Introduction to Classes.
Slide: 1 Copyright © AdaCore Subprograms Presented by Quentin Ochem university.adacore.com.
CS212: Object Oriented Analysis and Design Lecture 9: Function Overloading in C++
Chapter 3 Inheritance and Polymorphism Goals: 1.Superclasses and subclasses 2.Inheritance Hierarchy 3.Polymorphism 4.Type Compatibility 5.Abstract Classes.
C/C++ 3 Yeting Ge. Static variables Static variables is stored in the static storage. Static variable will be initialized once. 29.cpp 21.cpp.
C# Classes and Inheritance CNS 3260 C#.NET Software Development.
Inheritance, Polymorphism, And Virtual Functions Chapter 15.
What Is Inheritance? Provides a way to create a new class from an existing class New class can replace or extend functionality of existing class Can be.
CS212: Object Oriented Analysis and Design
12/9/20151 Programming Languages and Compilers (CS 421) Elsa L Gunter 2112 SC, UIUC Based in part on slides by Mattox.
CSC 143F 1 CSC 143 Constructors Revisited. CSC 143F 2 Constructors In C++, the constructor is a special function automatically called when a class instance.
C++ Inheritance Data Structures & OO Development I 1 Computer Science Dept Va Tech June 2007 © McQuain Generalization versus Abstraction Abstraction:simplify.
Chapter -6 Polymorphism
Do-it-yourself dictionaries in Haskell1 Ingmar Brouns & Lukas Spee.
Semantic Analysis II Type Checking EECS 483 – Lecture 12 University of Michigan Wednesday, October 18, 2006.
Types and Programming Languages Lecture 14 Simon Gay Department of Computing Science University of Glasgow 2006/07.
1 C# - Inheritance and Polymorphism. 2 1.Inheritance 2.Implementing Inheritance in C# 3.Constructor calls in Inheritance 4.Protected Access Modifier 5.The.
Recap Introduction to Inheritance Inheritance in C++ IS-A Relationship Polymorphism in Inheritance Classes in Inheritance Visibility Rules Constructor.
Terms and Rules II Professor Evan Korth New York University (All rights reserved)
CSE 332: C++ Overloading Overview of C++ Overloading Overloading occurs when the same operator or function name is used with different signatures Both.
C#.Net Software Development Version 1.0. Overview Inheritance Member Access Constructors Polymorphism (Name Hiding) Multilevel Hierarchy Virtual and VTable.
Arvind Computer Science and Artificial Intelligence Laboratory M.I.T. L05-1 September 21, 2006http:// Types and Simple Type.
Inheritance Modern object-oriented (OO) programming languages provide 3 capabilities: encapsulation inheritance polymorphism which can improve the design,
Programming Languages and Compilers (CS 421)
7. Inheritance and Polymorphism
Andy Wang Object Oriented Programming in C++ COP 3330
Inheritance and Polymorphism
Inheritance & Polymorphism
Mark Shields Oregon Graduate Institute Erik Meijer Utrecht University
Exceptions An exception signals an error, and has the ability to propagate upward through the call stack for easier management To “raise” an exception,
Overview of C++ Overloading
Pointers Dr. Bhargavi Goswami Department of Computer Science
Virtual Functions Polymorphism is supported by C++ both at compile time and at run time. Compile-time polymorphism is achieved by overloading functions.
Einführung in die Programmierung Introduction to Programming Prof. Dr
CISC/CMPE320 - Prof. McLeod
Einführung in die Programmierung Introduction to Programming Prof. Dr
Overview of C++ Polymorphism
VIRTUAL FUNCTIONS RITIKA SHARMA.
Inheriting Multiple Base Classes
Java Programming Language
Einführung in die Programmierung Introduction to Programming Prof. Dr
C++ Object Oriented 1.
Presentation transcript:

Reconciling OO and Haskell-Style Overloading Mark Shields Simon Peyton Jones Microsoft Research Cambridge

It's an OO World

Basic FFI Marshal C# base types to/from Haskell types Design decisions yet to make: –Unboxing? (Probably ignore) –Byrefs? (Probably copy in, copy out) –Null Pointers? (Probably use Maybe ) –Purity? Non-null? (Need standard C# type annotations) C#Haskell class A { static int m(int, char); } m :: Int -> Char -> IO Int

Novelty 1: Subtyping a :: A, b :: B a # m '1' :: IO Int b # m '1' :: IO Int (For simplicity, we'll ignore overriding in this talk. In practice, m may be redefined in a derived class.) C#Haskell class A { int m(char); } class B : A, I { … } m :: Char -> A -> IO Int (#) :: a -> (a -> b) -> b

Novelty 2: Overloading a :: A a # m '1' :: IO Int -- call first m a # m 1 :: IO Int -- call second m (This doesn't seem so bad, give Haskell already has type- based overloading) C#Haskell class A { int m(char); int m(int); } m :: Char -> A -> IO Int m :: Int -> A -> IO Int

Novelty 3: "Overlapping" Methods a :: A, c :: C, d :: D a # m c :: IO Int -- call first m a # m d :: IO Int -- call second m (Since both m 's match second call, this seems well outside of Haskell's system) C#Haskell class C { … } class D : C { … } class A { int m(C); int m(D); } m :: C -> A -> IO Int m :: D -> A -> IO Int

Objectives Account for these novelties in a way which: –is as natural as C#: no name mangling or explicit coercions –blends with existing constrained polymorphism and class constraints framework Non objectives: –Extend Haskell to be a "calculus of objects" in particular, internalizing virtual methods –be as natural as C# for constructing new classes –(The MLj approach would probably be the best were these to be the objectives)

Constrained Polymorphism in 1 Slide Classes: class D a => C a where a -> a Constraints: C  true if type  is a member of type class C Witnesses: instance C Int = t C Int is true, and witnessed by the pair (W, t) where W is a witness for D Int and t :: Int -> Int Type inference propagates constraints at compile-time Dictionary transformation causes witnesses to be propagated at run-time

Obvious Non-starter Can't just encode C# class as Haskell class: –Will reject C# class with overloaded methods (Haskell method names must be unique within class) –Will reject C# classes sharing the same method name (Haskell method names must be unique across all classes) –Too much witness passing: f :: A a => a -> IO Int Each call to f passes bogus dictionary of methods for a

Subtyping: Attempt Simulate subtyping constraints using classes: C#Haskell class A { int m(char); } class B : A, I { … } newtype A = ObjRef newtype B = ObjRef class BelowA a instance BelowA A instance BelowA B class (BelowA a, BelowI a) => BelowB a instance BelowB B m :: BelowA a => Char -> a -> IO Int

Subtyping: Attempt No witness passing required Works for multiple inheritance AboveA may be defined analogously Captures transitivity m :: BelowA a => Char -> a -> IO Int n :: BelowB a => Char -> a -> IO Int f :: BelowB a => a -> IO Int f x = do i <- x # m '1' j <- x # n '2' return i + j

Subtyping: Attempt  Quadratic growth in instance declarations  Almost no subtype simplifications possible (AboveA a, BelowA a) => a -> a instead of A -> A (AboveA a, BelowB a) => a -> a instead of type error

Subtyping: Solution Add a new subtype constraint t :<: u "type t is a subtype of type u " Subtype constraints only appear on –imported methods –coerce :: a : a -> b –(possibly) functions using the above Subtypes only introduced by newtype declarations newtype A :<: B 1,..., B n = ObjRef

Subtyping: Solution a :: A, b :: B a # m '1' :: IO Int b # m '1' :: IO Int f :: a : a -> IO Int f x = x # m '1' C#Haskell class A { int m(char); } class B : A, I { … } newtype A = ObjRef newtype B :<: A, I = ObjRef m :: a : Char -> a -> IO Int

Subtyping: Quirks For convenience, rules for structural subtyping builtin Subtyping is not implicit on every application Hence, maximization & minimization simplification rules not applicable (A : b -> a cannot be reduced to B -> A We only assume newtypes form a poset, not a lattice or upwards-closed semi-lattice Hence, no  or T types

Subtyping: Quirks The subtype poset is downward extensible Hence glb rules are not applicable newtype A newtype B newtype C :<: A, B (a : a -> a cannot be reduced to a : a -> a since another module may add newtype D :<: A, B lub rules ok

Overloading: Attempt Encode OO overloading as degenerate Haskell type-class overloading C#Haskell class A { int m(char); int m(int); } class Has_m a where m :: a instance a : Has_m (Char -> a -> IO Int) where m = {- call first m -} instance a : Has_m (Int -> a -> IO Int) where m = {- call second m -}

Overloading: Attempt  Type inference won't commit to return type: a :: A a # m '1' :: Has_m (Char -> A -> b) => b  Type inference won't report error eagerly: a # m True :: Has_m (Bool -> A -> b) => b  Type inference won't commit to a method: instance a : Has_m ((Int, Int) -> a -> IO Int) a # m (x,y) :: Has_m ((b,c) -> A -> d) => d

Overloading: Attempt These problems are a consequence of the openness of instance declarations: –simplifier must assume Has_m could be extended in another module –hence Has_m constraints will propagate within type schemes until become ground But, we want most/all method overloading to be resolved before generalization based on which methods are visible now

Overloading: Solution Introduce notion of closed class If simplifier sees a class constraint for a closed class, it may assume all instance declarations are in scope This allows more aggressive simplification: improvement and early failure Has_m classes are declared closed Constraint syntactic sugar m :: t for Has_m t

Overloading: Solution C#Haskell class A { int m(char); int m(int); } class closed m :: a where m :: a instance a : m :: Char -> a -> IO Int where m = {- call first m -} instance a : m :: Int -> a -> IO Int where m = {- call second m -}

Overloading: Solution Only one method on A with Char argument possible, so commit: a :: A a # m '1' :: IO Int No methods on A with Bool argument possible, so reject: a # m True error: cannot satisfy m :: Bool -> A -> c Only one method on A with pair argument, so commit: instance a : m :: (Int, Int) -> a -> IO Int a # m (x, y) :: IO Int (and x :: Int, y :: Int )

Overloading: Solution A little more subtle: still don't know which method, but know result must be IO Int a :: A f :: (m :: a -> A -> IO Int) => a -> IO Int f x = a # m x (Deferring method constraints seems reminiscent of virtual methods. We shall return to this)

Simplification for Closed Class Constraints Given constraint C  for closed class C : Step 1: Collect all "candidate" instances  =>  C  such that there exists a  such that  [  |   ] (C  C  and  [  |   ]  (may be) satisfiable Step 2: Calculate least-common generalization,  ', of all  [  |   ]  Step 3: Unify  with  ' Step 4: If only one candidate, use it to construct witness for C  '

Overlapping Overloading: Attempt Overlapping methods become overlapping instances C#Haskell class C { … } class D : C { … } class A { int m(C); int m(D); } class closed m :: a where m :: a instance (a : m :: b -> a -> IO Int instance (a : m :: b -> a -> IO Int

Overlapping Overloading: Attempt  Overlapping instances are illegal in standard Haskell  GHC/Hugs support them, but (as usual for Haskell...) without any formal description, and inconsistently (even between versions) Could be made to work if only one candidate: a :: A, c :: C a # m c -- only first m possible  But still fails with multiple candidates: a :: A, d :: D a # m d :: (m :: D -> A -> IO Int) => IO Int -- should choose second m

Overlapping Overloading: Solution Classes declared as closed (or overlap ) may have arbitrarily overlapping instances Solve class constraints as before, but if multiple candidates, may choose least under (an approximation of) the instantiation ordering  Eg:  a b. (a : m :: b -> a -> IO Int   a b. (a : m :: b -> a -> IO Int since from a :<: A, b :<: D we may derive a :<: A, b :<: C

Overlapping Overloading: Solution More formally,  is defined by entailment: However, it is implemented by simplification:  =>  C    '  |- e    '  C   =>  C  =>  C   =>  C    '  C     =>  C    '   =>  C  =>  C 

Overlapping Overloading: Incompleteness Constraint simplifier is deliberately incomplete w.r.t. constraint entailment for overlapping instances: –Only consider one class constraint at a time: instance m :: Int -> Int instance m :: Bool -> Int instance n :: Int -> Int instance n :: Char -> Int f x = m x + n x Valid typing is f :: Int -> Int But inferred type is f :: (m :: a -> Int, n :: a -> Int) => a -> Int

Overlapping Overloading: Incompleteness –Don't fully exploit unsatisfiability class closed C a where... class closed D a where... instance C Int instance C a => m :: a -> IO Int instance D a => m :: a -> IO Int f x = m x Valid typing is f :: Int -> IO Int But inferred type is f :: (m :: a -> IO Int) => a -> IO Int

Virtual Methods vs Member Constraints C#Haskell class A { virtual int m(); } class B : A { virtual int m(); } int f(A, A); f (x, y) { int i, j; i = x.m(); j = y.m(); return i + j } newtype A newtype B :<: A instance m :: A -> IO Int instance m :: B -> IO Int f :: (m :: a -> IO Int, m :: b -> IO Int) => a -> b -> IO Int f x y = do i <- x # m j <- y # m return i + j Dispatch based on dynamic type of x Call m passed as implicit argument

Virtual Methods vs Member Constraints Can we simulate OO references using existentials? Not quite: data Sub a = forall b. b : Sub b newtype A newtype B :<: A instance m :: A -> IO Int instance m :: B -> IO Int f = do a :: A <- new b :: B <- new list :: Sub A = [Sub a, Sub b] return (map (\Sub a -> m a) list) We'd like m to be chosen based on actual type of a error: constraint m :: a -> b escapes scope of existentially quantified variable a But, as stands, system cannot determine m from subtype constraint passed captured in Sub

Virtual Methods vs Member Constraints Upshot: it rarely makes sense for a method constraint to escape into a type scheme If class C has global modifier, it is an error for C  to appear in a scheme class global closed m :: a instance m :: A -> IO Int instance m :: B -> IO Int f x = x # m error: constraint m :: a -> b cannot be generalized

Type Class Space normaloverlapclosed local normal global Implicit Parameters HaskellGHC Methods functional dependencies Modifiers aren't as ad-hoc as may first appear:

Future Work About (finally...) to submit to Haskell or Babel Workshop Ulterior motive is to establish standard "calculus for type systems" including all the bells and whistles used in practice Simplifier defined quite abstractly still work to be done in refining it to a good algorithm... subtyping responsible for over half of complexity of simplifer Many small-scale FFI decisions to be made Simon & co about to start on implementation No formal properties shown as yet What about exporting Haskell as a.NET class? Can we further exploit nominal subtyping within Haskell?