Adding Pattern Matching to Existing Object-Oriented Languages

2 downloads 31490 Views 207KB Size Report
Oct 17, 2010 - Keywords Programming languages, pattern matching, object- oriented ..... 2 http://projectfortress.sun.com/Projects/Community/blog/ category/ ...
Adding Pattern Matching to Existing Object-Oriented Languages Sukyoung Ryu∗

Changhee Park∗

Guy L. Steele Jr.

KAIST [email protected]

KAIST [email protected]

Sun Labs, Oracle [email protected]

Abstract While object-oriented languages are designed for information hiding and data encapsulation, intensive data manipulation often calls for pattern matching, one of the main features of functional programming languages. Pattern matching provides a concise way to describe specific structures or conditions of objects so that programmers can clearly identify and easily access the corresponding objects. The need for both object information hiding and pattern matching over objects, which are seemingly conflicting features, shows up very well in the problem of language manipulation. In the compiler development of Fortress, a new object-oriented language with extensive support for functional programming, we originally implemented the Fortress type checker in Java. However, because Java does not provide pattern matching, we reimplemented the type checker in Scala. Compared to the pattern matching mechanism in Scala, Java’s visitor patterns are verbose, make it hard to capture the high-level specification of objects, and are nontrivial to maintain. In this paper, we introduce a new pattern-matching mechanism which can be added to existing object-oriented languages. We present the mechanism currently being implemented as an addition to the Fortress programming language. The mechanism provides a concise way of describing and using patterns in the presence of runtime manipulation of types using typecase and multimethods, unlike Scala which has type-erasure semantics. Even though it is premature to discuss the performance and scalability of the presented mechanism, we believe that the same optimization techniques used in Scala will apply to our mechanism.

trait TreeJT K getter item(): T getter depth(): Z32 end ` ´ object NodeJT K left: TreeJT K, item: T, right: TreeJT K extends TreeJT K getter depth(): Z32 = 1 + (left.depth MAX right.depth) end object LeafJT K(item: T ) extends TreeJT K getter depth(): Z32 = 1 end Figure 1. Tree structure in an object-oriented language

Categories and Subject Descriptors D.3.1 [Programming Languages]: third-level; D.3.3 [Programming Constructs and Features]: third-level

Figure 1 written in a new object-oriented language with extensive support for functional programming, Fortress [3]. In Fortress, traits are like Java [9] interfaces but may contain code, and objects are like Java final classes that may not be extended. The trait TreeJT K represents a binary tree, which has items of type T . Each tree is either an internal node, NodeJT K , or a leaf node, LeafJT K , with an item . An internal node has two more fields: a left subtree and a right subtree. In Fortress, the value parameters of object constructors define fields and the corresponding getters. Following the convention of object-oriented languages, the getter method depth is encapsulated in the trait TreeJT K and its implementation is hidden in the implementing objects NodeJT K and LeafJT K . One can define binary trees as: ` ´ NodeJStringK LeafJStringK(“a”), “b”, LeafJStringK(“c”)

General Terms Languages, Design, Pattern Matching

or:

Keywords Programming languages, pattern matching, objectoriented languages, Fortress,

1.

Introduction

Two main ideas of object-oriented languages are information hiding and data encapsulation. Consider the binary tree declarations in

` NodeJZ32K LeafJZ32K(1), 2, ` ´´ NodeJZ32K LeafJZ32K(3), 4, LeafJZ32K(5) and explicit type arguments to the node constructors may be omitted with type inference as: ` ´ Node Leaf(“a”), “b”, Leaf(“c”) or:

* Supported in part by the Engineering Research Center of Excellence Program of Korea Ministry of Education, Science and Technology(MEST) / National Research Foundation of Korea(NRF) (Grant 2010-0001723).” Permission to make digital or hard copies of all or part of this work for personal or classroom use is granted without fee provided that copies are not made or distributed for profit or commercial advantage and that copies bear this notice and the full citation on the first page. To copy otherwise, to republish, to post on servers or to redistribute to lists, requires prior specific permission and/or a fee. FOOL ’10 October 17, 2010, Reno, Nevada, USA. c 2010 ACM [to be supplied]. . . $5.00 Copyright

` ´ Node Leaf(1), 2, Node(Leaf(3), 4, Leaf(5)) While information hiding and data encapsulation serve very well for high-level data abstraction, they do not work well for intensive data manipulation. Let us assume that we want to get the sum of all items in a tree of type TreeJZ32K by calling a function sum . Instead of calling the function for every node in the tree, we want to perform a simple optimization so that when a subtree of an internal node is a leaf node, we get its item via a field access without calling the function for the node. In order to perform

trait TreeJT K getter item(): T getter depth(): Z32 end ` ´ object NodeJT K left: TreeJT K, item: T, right: TreeJT K extends TreeJT K getter depth(): Z32 = 1 + (left.depth MAX right.depth) end object LeafJT K(item: T ) extends TreeJT K getter depth(): Z32 = 1 end ` ´ sum t: TreeJZ32K : Z32 = ˆˆ ˜˜ if instanceOf NodeJZ32K (t) ˆˆ ˜˜ then n = cast NodeJZ32K (t) ˆˆ ˜˜ if instanceOf LeafJZ32K (n.left) ˆˆ ˜˜ then if instanceOf LeafJZ32K (n.right) then n.left.item + t.item + n.right.item else n.left.item + t.item + sum(n.right) end ˆˆ ˜˜ else if instanceOf LeafJZ32K (n.right) then sum(n.left) + t.item + n.right.item else sum(n.left) + t.item + sum(n.right) end end else t.item end Figure 2. Tree manipulation using instanceOf and cast functions

the optimization, we need to identify such an internal node and to access the field of the node. As Burak Emir et al. [7] described, standard object-oriented techniques are not satisfactory in terms of conciseness of expressions and program maintainability. Using a series of code examples adapted from Burak Emir [6], we illustrate the issues with the standard object-oriented techniques. The most primitive way to inspect the tree structure would use “instanceof” testing followed by “casting”. Although Fortress does not provide them as built-in operators, it implements instanceOf and cast as normal Fortress functions in a library for convenience1 . Figure 2 presents an example implementation of the above mentioned optimization using instanceOf and cast . The function sum is called for each node of the given tree t , while skipping some subtrees to perform the optimization. While the technique does not require any modifications to the existing trait and object declarations, using the instanceOf testing and manually casting the objects to more specific types are very verbose and error prone. In classical object-oriented decomposition, the trait TreeJT K contains classification methods and accessor methods to identify the actual objects and to access their fields, and the implementing objects override the corresponding methods as presented in Figure 3. To identify the nodes to skip and to access their fields, the function uses the classification and accessor methods declared in TreeJT K . Compared to the instanceOf testing with casting, object-oriented decomposition is slightly more systematic but it 1 http://projectfortress.sun.com/Projects/Community/

browser/trunk/Library/FortressLibrary.fss

trait TreeJT K getter item(): T getter depth(): Z32 (* classification methods *) getter isNode() = false getter isLeaf () = false (* accessor methods *) getter left(): TreeJT K = throw NotFound getter right(): TreeJT K = throw NotFound end ` ´ object NodeJT K left: TreeJT K, item: T, right: TreeJT K extends TreeJT K getter depth(): Z32 = 1 + (left.depth MAX right.depth) getter isNode() = true end object LeafJT K(item: T ) extends TreeJT K getter depth(): Z32 = 1 getter isLeaf () = true end ` ´ sum t: TreeJZ32K : Z32 = if t.isNode then if t.left.isLeaf then if t.right.isLeaf then t.left.item + t.item + t.right.item else t.left.item + t.item + sum(t.right) end else if t.right.isLeaf then sum(t.left) + t.item + t.right.item else sum(t.left) + t.item + sum(t.right) end end else t.item end Figure 3. Tree manipulation in object-oriented decomposition

adds clutters to the common supertype TreeJT K to use in its subtypes NodeJT K and LeafJT K . The visitor design pattern [8] allows programmers to provide additional functionalities to an existing type hierarchy. Figure 4 presents the same optimization using visitors. Compared to the object-oriented decomposition technique, the visitor pattern provides a systematic way of handling each case of the tree hierarchy. It does not pollute the declaration of the TreeJT K trait by requiring the classification and accessor methods; it requires only a single method accept so that the visitor can manipulate the tree data. The trait TreeVisitorJT, SK supplies forNode and forLeaf methods which understand the tree structure and visit each field of the nodes. It also supplies forNodeOnly and forLeafOnly methods which are abstract so that the implementing visitor objects should implement an actual operation on each node. Because the visitor pattern can only distinguish on one level, Figure 4 defines two visitor objects Adder and IsLeaf to identify the target nodes for the optimization. Instead of the long and verbose design patterns and idioms, a language construct can manipulate data types more concisely. Figure 5 presents the sum function using a typecase expression. The typecase expression inspects the run-time type of the given

trait TreeVisitorJT, SK ` ´ forNode t: NodeJT K : S = forNodeOnly(t.left, t.item, t.right) ` ´ forLeaf t: LeafJT K : S = forLeafOnly(t.item) ` ´ forNodeOnly left: TreeJT K, item: T, right: TreeJT K : S forLeafOnly(item: T ): S end trait TreeJT K getter item(): T getter depth(): Z32 ` ´ acceptJSK v: TreeVisitorJT, SK : S end ` ´ object NodeJT K left: TreeJT K, item: T, right: TreeJT K extends TreeJT K getter depth(): Z32 = 1 + (left.depth MAX right.depth) ` ´ acceptJSK v: TreeVisitorJT, SK : S = v.forNode(self) end object LeafJT K(item: T ) extends TreeJT K getter depth(): Z32 = 1 ` ´ acceptJSK v: TreeVisitorJT, SK : S = v.forLeaf (self) end object Adder extends TreeVisitorJZ32, Z32K ` ´ forNodeOnly l: TreeJZ32K, item: Z32, r: TreeJZ32K = if l.accept(IsLeaf) then if r.accept(IsLeaf) then l.item + item + r.item else l.item + item + r.accept(self) end else if r.accept(IsLeaf) then l.accept(self) + item + r.item else l.accept(self) + item + r.accept(self) end end forLeafOnly(item: Z32) = item end object IsLeaf extends TreeVisitorJZ32, BooleanK ` ´ forNodeOnly l: TreeJZ32K, item: Z32, r: TreeJZ32K = false forLeafOnly(item: Z32) = true end ` ´ sum t: TreeJZ32K = t.accept(Adder) Figure 4. Tree manipulation using visitors

trait TreeJT K getter item(): T getter depth(): Z32 end ` ´ object NodeJT K left: TreeJT K, item: T, right: TreeJT K extends TreeJT K getter depth(): Z32 = 1 + (left.depth MAX right.depth) end object LeafJT K(item: T ) extends TreeJT K getter depth(): Z32 = 1 end ` ´ sum t: TreeJZ32K : Z32 = typecase t of t0 : NodeJZ32K ⇒ typecase (t0 .left, t0 .right) of ` ´ l: LeafJZ32K, r: LeafJZ32K ⇒ l.item + t.item + r.item ` ´ l: LeafJZ32K, r: TreeJZ32K ⇒ l.item + t.item + sum(r) ` ´ l: TreeJZ32K, r: LeafJZ32K ⇒ sum(l) + t.item + r.item ` ´ l: TreeJZ32K, r: TreeJZ32K ⇒ sum(l) + t.item + sum(r) end LeafJZ32K ⇒ t.item end Figure 5. Tree manipulation using typecase

tree t to check whether the tree is eligible for the optimization. Like the visitor pattern, the typecase expression can only distinguish on one level, which required sum use two-level typecase expressions. If the tree example was not real enough to show the need for better support for object inspection in an object-oriented language, which are seemingly conflicting features, our own experience in the compiler development of Fortress gives a realistic example. Like for most of the statically typed languages, the Fortress compiler consists of various compiler phases that manipulate Abstract Syntax Tree (AST) intensively. We originally implemented the Fortress type checker in Java mainly because the Fortress parser is automatically generated in Java and the Fortress back end emits Java bytecode. However, because Java does not provide a good way to manipulate ASTs, we reimplemented the type checker in Scala using its “pattern-matching” mechanisms. Compared to the patternmatching mechanisms in Scala, Java’s visitor patterns are verbose, make it hard to capture the high-level specification of objects, and are nontrivial to maintain. To overcome the limitations of the standard object-oriented techniques shown so far, Scala [15] added mechanisms for supporting pattern matching. Pattern matching is one of the main features of functional programming languages. It provides a concise way to describe specific structures or conditions of objects so that programmers can clearly identify and easily access the corresponding objects. Scala added two mechanisms to support pattern matching: case classes and extractors. Case classes are regular classes prefixed with the case modifier; they export their constructor parameters so that they can be usable in pattern matching. As Figure 6 presents, two classes Node and Leaf are prefixed with the case modifier and the sum function uses pattern matching over the structure of the given tree. Compared to the standard object-oriented techniques, case classes require only the case modifier for the classes that are intended to be used in

trait Tree[T] { def item():T def depth():Int } case class Node[T](left:Tree[T], item:T, right:Tree[T]) extends Tree[T] { override def depth() = 1 + max(left.depth, right.depth) } case class Leaf[T](item:T) extends Tree[T] { override def depth() = 1 } def sum(t:Tree[Int]):Int = t match { case Leaf(i) => i case Node(Leaf(l),i,Leaf(r)) => l + i + r case Node(Leaf(l),i,r) => l + i + sum(r) case Node(l ,i,Leaf(r)) => sum(l) + i + r case Node(l ,i,r) => sum(l) + i + sum(r) } Figure 6. Tree manipulation using case classes in Scala trait Tree[T] { def item():T def depth():Int } class Node[T](val left:Tree[T], val item:T, val right:Tree[T]) extends Tree[T] { override def depth() = 1 + max(left.depth, right.depth) } class Leaf[T](val item:T) extends Tree[T] { override def depth() = 1 } object Node { def apply[T](left:Tree[T],item:T,right:Tree[T]) = new Node(left, item, right) def unapply[T](n:Node[T]) = Some(n.left, n.item, n.right) } object Leaf { def apply[T](item:T) = new Leaf(item) def unapply[T](l:Leaf[T]) = Some(l.item) } def sum(t:Tree[Int]):Int = t match { case Leaf(i) => i case Node(Leaf(l),i,Leaf(r)) => l + i + r case Node(Leaf(l),i,r) => l + i + sum(r) case Node(l ,i,Leaf(r)) => sum(l) + i + r case Node(l ,i,r) => sum(l) + i + sum(r) } Figure 7. Tree manipulation using extractors in Scala

pattern matching, and they support nested pattern matching in a very concise way. However, because the existence of the case modifier determines whether a class is usable in pattern matching, one needs to make that decision at a class declaration site. Scala also added a mechanism for user-defined patterns, extractors [7, 6]. Instead of case classes, one can define an extractor object with two special methods: apply and unapply. Figure 7 shows an example implementation using extractors. Unlike in Figure 6, classes Node and Leaf are not case classes but they have corresponding extractor objects, Node and Leaf. When such an extractor object appears in pattern matching as follows: t match { case Leaf(i) => i ... } the pattern “case Leaf(i)” invokes Leaf.unapply, which returns the item of the leaf node (wrapped to denote that the match succeeded). In effect, it desugars to the following: Leaf.unapply(t) match { case Some(i) => i case None => ... } The apply method is not necessary for pattern matching but serves for a constructor; while an extractor object is a singleton object, the apply method allows a concise constructor syntax. For example, Leaf(5) desugars to Leaf.apply(5). Extractors provide a very powerful way to define patterns for the existing non-case classes. However, they require an extractor object for each class to be usable in pattern matching. Extractors are not limited to existing classes; they can be entirely independent of actual implementation. Adapted from Emir et at. [7], consider the following trait of complex numbers: trait Complex case class Cart(re: double, im: double) extends Complex while the actual implementation uses Cartesian coordinates, the following extractor object can hide the implementation detail: object Polar { def apply(mod:double, arg:double):Complex = new Cart(mod * Math.cos(arg), mod * Math.sin(arg)) def unapply(z:Complex):Option[(double,double)] = z match { case Cart(re, im) => val at = atan(im / re) Some(sqrt(re * re + im * im), if (re < 0) at + Pi else if (im < 0) at + Pi * 2 else at) } } and allows programmers to use polar coordinates instead. Inspired by Scala and Syme et al [19], we introduce a new pattern-matching mechanism which can be added to existing object-oriented languages. We present the mechanism currently being implemented as an addition to the Fortress programming language. The mechanism provides a concise way of describing and using patterns in the presence of run-time manipulation of types using typecase and multimethods, unlike Scala which has type-erasure semantics. Even though it is premature to discuss the performance and scalability of the presented mechanism, we be-

˘ ¯ trait TreeJT K(item: T ) comprises NodeJT K, LeafJT K getter depth(): Z32 end ` ´ object NodeJT K left: TreeJT K, item: T, right: TreeJT K extends TreeJT K getter depth(): Z32 = 1 + (left.depth MAX right.depth) end object LeafJT K(item: T ) extends TreeJT K getter depth(): Z32 = 1 end ` ´ sum t: TreeJZ32K = typecase t of LeafJZ32K(i) ⇒ i ` ´ NodeJZ32K LeafJZ32K(l), i, LeafJZ32K(r) ⇒ l + i + r ` ´ NodeJZ32K LeafJZ32K(l), i, r ⇒ l + i + sum(r) ` ´ NodeJZ32K l, i, LeafJZ32K(r) ⇒ sum(l) + i + r NodeJZ32K(l, i, r) ⇒ sum(l) + i + sum(r) end

inherited by only the classes in the same source file, the types listed in the comprises clause of a trait do not need to be in the same source file and the comprises clause may include naked type variables to encode the self type idiom 2 . The comprises clause is not necessary for pattern matching but it allows for the compiler to perform more static checks such as coverage checks. The sum function uses patterns via a typecase expression´ including nested ` patterns such as NodeJZ32K l, i, LeafJZ32K(r) . Unlike Java and Scala which use type-erasure semantics, Fortress preserves type arguments at run time. Maintaining generic types at run time helps programmers write cleaner, more reliable code [1]. Therefore, one can use typecase expressions to distinguish between binary trees with integer items and binary trees with string items: ` ´ kind JT K t: TreeJT K = typecase t of TreeJZ32K ⇒ “Integer binary tree” TreeJStringK ⇒ “String binary tree” else ⇒ “Other binary tree” end 2.2

Figure 8. Tree manipulation using Fortress pattern matching lieve that the same optimization techniques used in Scala should apply to our mechanism.

2.

Pattern Matching in Fortress

Fortress is an object-oriented programming language with extensive support for functional programming. Fortress organizes objects into a multiple-inheritance hierarchy based on traits and objects; traits are internal nodes and objects are leaves of a type hierarchy. To support multiple inheritance, traits may not have fields but only objects may have fields. Fortress has a typecase expression; it inspects only the run-time type of the value of a given expression instead of its structure or any fields of it. To support pattern matching in Fortress, we modestly modified the existing language to define and use patterns seamlessly. First, we present the tree example introduced in Section 1 using the pattern-matching mechanism in Fortress. After that, we incrementally describe how we extend Fortress with the pattern-matching mechanism. 2.1

Example Revisited

In the Fortress pattern-matching mechanism, patterns may be defined by traits and objects with value parameters. In Figure 8, for example, the NodeJT K object declares value parameters left , item , and right so that NodeJZ32K(l, i, r) could be used as a pattern. The TreeJT K trait declares a value parameter item but, unlike objects, the value parameter does not implicitly define a field, which is good because traits may not have fields. Indeed, the Fortress compiler desugars the declaration as follows: ˘ ¯ trait TreeJT K comprises NodeJT K, LeafJT K getter item(): T getter depth(): Z32 end The TreeJT K trait has also a comprises clause, which means that only NodeJT K and LeafJT K are subtypes of TreeJT K , and a value of type TreeJT K should be of type either NodeJT K or LeafJT K . The comprises clause is similar to, but more general than sealed classes in Scala. While a sealed class may be directly

Patterns

Trait and object declarations with value parameters introduce types that may be used as patterns. Object declarations may have value parameters, which declare object constructors. We extend the syntax of trait declarations to allow value parameters; it is similar in syntax and spirit to the value parameters in an object declaration, but for a trait no constructor function is created. Value parameters of a trait or an object declaration determine a standard form of the patterns for the type. As shown in Figure 8, three value parameters of the NodeJT K object allows programmers to use NodeJZ32K(l, i, r) as a pattern. Similar to the object constructors, explicit type arguments to the types in patterns may be omitted with type inference. A pattern consists of either (a) a type for a declared trait or object followed by a list of pattern parameters in parentheses separated by commas (intended to match a value of the indicated type), or (b) just a list of pattern parameters in parentheses separated by commas (intended to match a void, single values, or tuples). We call the former a type pattern and the latter a tuple pattern. A pattern parameter is either (a) a pattern variable, (b) a pattern variable annotated with a type or a pattern, (c) a type or a pattern, and (d) a keyword pattern parameter, which is one of the above three cases, (a) through (c), followed by “=” and a field name: Pattern PatternParam

TypeOrPattern

::= | ::= | | | | | ::= |

Type(PatternParam) (PatternParam) PatternVar PatternVar : TypeOrPattern TypeOrPattern PatternVar = FieldName PatternVar : TypeOrPattern = FieldName TypeOrPattern = FieldName Type Pattern

When a pattern is matched against a value, the pattern parameters are bound to the values that are obtained from the value by using certain getters. These getters will always include those for the plain (non-keyword, non-varargs) value parameters of the object or trait mentioned in the pattern, and the plain (non-keyword) pattern parameters of the pattern will be bound to the values ob2 http://projectfortress.sun.com/Projects/Community/blog/

category/SelfTypes

tained by these getters. In addition, if the pattern includes keyword pattern parameters, then additional getters will be used to satisfy those keyword pattern parameters; what follows the “=” in a keyword pattern parameter in a pattern is the name of a getter for the value that is being matched to the pattern. The compiler signals a static error if the type named in the pattern does not have a getter of that name. Patterns may be used in various places, such as variable declarations, function expressions, function or method declarations, and typecase expressions. The Fortress compiler desugars language constructs using patterns in all situations other than typecase expressions into the ones without any patterns. This desugaring mechanism requires modest cost to add pattern matching to an existing language. We describe the desugaring mechanism primarily through examples in the subsequent sections, and we explain the patterns in typecase expressions in Section 2.8. 2.3

Patterns in Variable Declarations

Variable declarations may use patterns to decompose a data type and declare multiple variables for its components. While Fortress provides both top-level and local variable declarations, the Fortress compiler desugars patterns in any variable declarations in the same way. The following two declarations declare variables with patterns, Tree(k) and Node(l, i, r) : tree 1 : Tree(k) = Leaf(“a”) tree 2 : Node(l, i, r) = Node(tree 1 , “b”, tree 1 ) Because Leaf is a subtype of Tree and the trait Tree declares a value parameter item , the value Leaf(“a”) matches to the pattern Tree(k) and the pattern variable k is bound to the value “a” . Similarly, the pattern variables l , i , and r are bound to the values tree 1 , “b” , and tree 1 , obtained by the getters left , item , and right of the object Node. These declarations are desugared as follows: tree 1 : TreeJStringK = Leaf(“a”) k = tree 1 .item tree 2 : NodeJStringK = Node(tree 1 , “b”, tree 1 ) (l, i, r) = (tree 2 .left, tree 2 .item, tree 2 .right) A pattern may include pattern variables with explicit type annotations to be useful for type checking, or keyword pattern parameters to use fields that are not value parameters of the enclosing trait or object in a pattern. For example, in the following declaration: ` ´ tree 3 : Node l0 : TreeJStringK, i0 : String, r0 , d = depth = Node(tree 2 , “c”, tree 1 ) the pattern variables, l0 and i0 have type annotations, TreeJStringK and String , respectively. The keyword pattern parameter d allows the field depth , which is not a value parameter of the object Node, to occur in a pattern. Because keyword pattern parameters are optional unlike non-keyword pattern parameters, the keyword pattern parameters should appear after any non-keyword pattern parameters. For example, a pattern such as: Node(l0 = left, i0 , d = depth, r0 ) is syntactically illegal. However, the keyword pattern parameters may refer to any value parameters of a trait or an object, and the order between keyword pattern parameters does not matter; for example: Node(l0 , i0 , d = depth, r0 = right) is a syntactically legal pattern and it denotes the same pattern as:

Node(l0 , i0 , r0 , d = depth). The above declaration with a keyword pattern parameter is desugared as follows: tree 3 : NodeJStringK = Node(tree 2 , “c”, tree 1 ) `0 ´ l : TreeJStringK, i0 : String, r0 , d = (tree 3 .left, tree 3 .item, tree 3 .right, tree 3 .depth) Finally, pattern-matching desugaring preserves variable modifiers. A variable in Fortress may have modifiers such as var which denotes that the variable is mutable, and they are propagated into the pattern variables in a pattern annotated to the variable declaration. For example, the following variable declaration: var tree 4 : Tree(k0 ) = Node(tree 1 , “h”, tree 3 ) desugars to the following: var tree 4 : TreeJStringK = Node(tree 1 , “h”, tree 3 ) var k0 = tree 4 .item Because the variable tree 4 has the modifier var , the pattern variable k0 also has the modifier var . As we have shown so far, the pattern-matching desugaring mechanism rewrites a variable declaration with patterns to multiple declarations without patterns. By removing the newly added patterns in the front end of the Fortress compiler, the compiler back end remains intact. 2.4

Nested Patterns

Patterns may be nested arbitrarily: ` ´ tree 5 : Node Node(l00 , i00 , r00 ), item, right: Leaf(i000 ) = ` ´ Node tree 2 , “i”, Leaf(“a”) The pattern annotated to the variable tree 5 contains nested patterns, Node(l00 , i00 , r00 ) and Leaf(i000 ) . The pattern-matching desugarer desugars the declaration in the same way as presented in the previous sections repeatedly until no more patterns remain. First, it desugars the outer-most declaration by introducing a pattern variable named temp$1 to bind it in a nested pattern: ` ´ tree 5 : NodeJStringK = Node tree 2 , “i”, Leaf(“a”) ` ´ temp$1: Node(l00 , i00 , r00 ), item, right: Leaf(i000 ) = (tree 5 .left, tree 5 .item, tree 5 .right) Because more patterns still remain in the second declaration, the desugarer rewrites it repeatedly until no patterns exist: tree 5 : NodeJStringK = Node(tree 2 , “i”, Leaf(“a”)) (temp$1: NodeJStringK, item, right: LeafJStringK) = (tree 5 .left, tree 5 .item, tree 5 .right) (l00 , i00 , r00 ) = (temp$1.left, temp$1.item, temp$1.right) i000 = right.item 2.5

Patterns in Tuple

As we described in Section 2.2, a pattern is either a type pattern or a tuple pattern. When a type pattern matches to a value, its pattern parameters are accessed via field references. However, when a tuple pattern matches to a value, its pattern parameters cannot be accessed via tuple deconstructors because Fortress does not provide such a language construct. Instead, Fortress implements tuple deconstructors as library functions. While it is possible to desugar tuple patterns using such library functions, we chose to decompose a tuple pattern declaration with an additional declaration using a non-pattern tuple type to simplify the desugaring code. For example, the following declaration using a tuple pattern: tuple tree: (Leaf(i0t ), Node(lt , it , rt )) = (tree1 , tree2 )

desugars to decompose the tuple pattern as follows: tuple tree: (LeafJStringK, NodeJStringK) = (tree1 , tree2 ) (temp$2: Leaf(i0t ), temp$3: Node(lt , it , rt )) = tuple tree so that the tuple components can bind the pattern variables temp$2 and temp$3 . There still remain patterns in the desugared declaration; the desugarer desugars them away repeatedly as follows: tuple tree: (LeafJStringK, NodeJStringK) = (tree1 , tree2 ) (temp$2: LeafJStringK, temp$3: NodeJStringK) = tuple tree i0t = temp$2. item (lt , it , rt ) = (temp$3. left, temp$3. item, temp$3. right) 2.6

Patterns in Function Expressions

Parameters of function expressions may have patterns: ` ´ fn node: NodeJZ32K(lf , if , rf ) : NodeJZ32K ⇒ Node(rf , if , lf ) The above function expression takes a parameter, node , of type NodeJZ32K and returns a new node with the swapped subtrees of node . The parameter node has a pattern, NodeJZ32K(lf , if , rf ) , to directly access the fields of node by deconstructing it. The parameters in a function expression are desugared in a similar way, by wrapping a block around the body to add the new declarations introduced by the desugarer: ` ´ fn node: NodeJZ32K : NodeJZ32K ⇒ do (lf , if , rf ) = (node.left, node.item, node.right) Node(rf , if , lf ) end 2.7

Patterns in Functions, Methods, and Functional Methods

In addition to conventional functions and “dotted methods”, Fortress provides functional methods. Functional methods are declared within traits and may be inherited like dotted methods, but are invoked by ordinary function calls rather than conventional “dotted method calls.” Functions, dotted methods, and functional methods are collectively called functionals. Parameters of functionals may have patterns. The patternmatching desugarer handles patterns in the parameters of functionals in the same way regardless of the forms of the functionals. Desugaring patterns in parameters of functionals is the most interesting case, because it should consider the interactions of patterns with overloading. In Fortress, functionals may be overloaded; there may be multiple functional declarations with the same name. For a given functional call, Fortress invokes the most specific functional declaration using symmetric multiple dynamic dispatch [4]. For example, the declarations of rotate presented in Figure 9 each declares an overloaded function that, if possible, rotates a portion of a tree. The first declaration specifies the most general signature of the other three overloaded definitions. Note that the second declaration includes a pattern variable y , though it is not used in the function body, to show that a nested pattern in parameters of functionals may annotate a pattern variable. The pattern-matching desugarer desugars a functional declaration that includes patterns in its parameters into two functional declarations: the first of which has a fresh but hidden name that is systematically generated from the original name of the functional, and the second has the same name as the original function but its parameters do not include patterns and its body calls the freshly named function. The parameters of the freshly named function consist of the parameters of the original function and the pattern variables in the patterns included in the original function declaration. For example, because the second and the third declarations in Figure 9 include patterns, they are desugared as in Figure 10. Note that the desugaring process will sometimes generate identical function

DUPLICATE OKAY” definitions, as indicated by the comments “* in Figure 10; because such duplicate definitions are generally not permitted in Fortress, redundant definitions created by the patternmatch desugaring are detected and eliminated just after the desugaring step and before the next compilation step. After one step of desugaring, there still remain patterns in the desugared declarations. The pattern-matching desugarer desugars the remaining patterns away until there are no more patterns in the generated declarations. One more step of desugaring the second declaration in Figure 10 eliminates all the patterns in the set of declarations. Figure 11 presents the final result of desugared declarations after removing any duplicated function declarations generated by the desugarer.

2.8

Typecase Expressions

A typecase expression in Fortress enables programmers to inspect the run-time type of the value of a given expression. The syntax of a typecase expression is as follows: typecase e of clause 1 ⇒ body 1 clause 2 ⇒ body 2 ... else ⇒ body n end where each clause is a type. The value of e is “matched” to each clause in order and the body expression of the first matching clause is evaluated. A value of the run-time type τ matches to a typecase clause σ, if τ is a subtype of σ. While both the functional overloading and typecase expressions of Fortress use the run-time type of a given expression to choose an expression to evaluate among multiple alternatives, one cannot be desugared to the other. As our previous work [4] describes, a valid set of overloaded functional declarations should guarantee that there exists the best function declaration to pick at run time, no matter in which order the functionals are declared. However, the order of the clauses of a typecase expression matters; the run-time type of a given expression is compared to the type in each clause of a typecase expression in order and the first matching clause is selected. We extend the syntax of a clause from a type to either a pattern variable, a type or a pattern possibly prefixed by a pattern variable. Then, a value v of the run-time type τ matches to a typecase clause σ according to the following matching process: 1. When σ is a pattern variable, v matches to σ and the pattern variable is bound to v. 2. When σ is a type, v matches to σ if τ is a subtype of σ. If σ is prefixed by a pattern variable, the pattern variable is bound to v. 3. When σ is a type pattern T (π), v matches to σ if τ is a subtype of T and the corresponding fields of v match to π. If σ is prefixed by a pattern variable, the pattern variable is bound to v. 4. When σ is a tuple pattern, v matches to σ if v is a tuple, σ and v have the same number of elements, and each element of v matches to the corresponding element of σ. If σ is prefixed by a pattern variable, the pattern variable is bound to v. We can rewrite the rotate function declared in Figure 9 by using a typecase expression as shown in Figure 12. Instead of overloaded declarations, a single rotate declaration handles multiple cases of the given tree x . While the order of the overloaded declarations does not matter, the order of the typecase clauses

` ´ rotate x: TreeJZ32K : TreeJZ32K ` ´ ` ´ rotate x: NodeJZ32K(y: NodeJZ32K(p, q, r), s, t) = Node p, q, Node(r, s, t) ` ´ rotate x: NodeJZ32K(LeafJZ32K, , ) = x ` ´ rotate x: LeafJZ32K = x Figure 9. Overloaded function declarations including patterns ` ´ rotate x: TreeJZ32K : TreeJZ32K ` ´ ` ´ rotate$match x: NodeJZ32K, y: NodeJZ32K(p, q, r), s: Z32, t: TreeJZ32K = Node p, q, Node(r, s, t) ` ´ * DUPLICATE OKAY rotate x: NodeJZ32K = rotate$match(x, x.left, x.item, x.right) ` ´ rotate$match x: NodeJZ32K, temp$4: LeafJZ32K, : Z32, : TreeJZ32K = x ` ´ * DUPLICATE OKAY rotate x: NodeJZ32K = rotate$match(x, x.left, x.item, x.right) ` ´ rotate x: LeafJZ32K = x Figure 10. Overloaded function declarations including patterns desugared once ` ´ rotate x: TreeJZ32K : TreeJZ32K ` ´ ` ´ rotate$match$match x: NodeJZ32K, y: NodeJZ32K, p: TreeJZ32K, q: Z32, r: TreeJZ32K, s: Z32, t: TreeJZ32K = Node p, q, Node(r, s, t) ` ´ rotate$match x: NodeJZ32K, y: NodeJZ32K, s: Z32, t: TreeJZ32K = rotate$match$match(x, y, y.left, y.item, y.right, s, t) ` ´ rotate$match x: NodeJZ32K, temp$4: LeafJZ32K, : Z32, : TreeJZ32K = x ` ´ rotate x: NodeJZ32K = rotate$match(x, x.left, x.item, x.right) ` ´ rotate x: LeafJZ32K = x Figure 11. Overloaded function declarations without any patterns ` ´ rotate x: TreeJZ32K : TreeJZ32K = typecase x of ` ´ ` ´ NodeJZ32K y: NodeJZ32K(p, q, r), s, t ⇒ Node p, q, Node(r, s, t) ´ ` NodeJZ32K LeafJZ32K, , ⇒ x LeafJZ32K ⇒ x end Figure 12. Rewritten rotate introduced in Figure 9 using typecase matters because the clauses are matched in order. Recall that, by the comprises clause in the definition of the trait Tree in Figure 8, a value of type TreeJT K is either a value of type NodeJT K or a value of type LeafJT K . Therefore, the typecase expression in Figure 12 does not need any else clause because it covers all the possible cases of the parameter x . The matching process including pattern variable bindings described so far happens at run time, not at compile time. Therefore, the pattern-matching desugaring mechanisms for patterns, which we have presented in the previous sections, are not applicable to typecase expressions. The reason for this difference is that in the situations addressed by previous sections, the pattern match is guaranteed to succeed, thanks to static type checking, whereas one of the purposes of pattern matching in a typecase expression is to find out whether a match will succeed or fail. Until now we showed how to add our pattern matching mechanism to the Fortress programming language. To apply the mechanism to an existing object-oriented language, the language should include the following features:

1. value parameters The language should hava a way to declare an object or a class possibly including some fields as value parameters. 2. getter methods The language should have getter methods to provide a way to use pattern matching over fields that are not value parameters. 3. type annotations The language should allow the programmers to annotate types explicitly so that they can annotate patterns in place of types to use pattern matching. 4. closed data types (optional) If the language provides closed data types, the compiler can perform more static checks such as exhaustiveness and redundancy checks for pattern matching.

3.

Implementation

We are currently implementing the pattern-matching mechanism described in this paper in the Fortress compiler [2]. Adding pattern matching to the Fortress programming language consists of three steps: (1) changing syntax, (2) adding a pattern-matching desugarer, and (3) handling typecase expressions. To add pattern matching to Fortress, we changed the syntax of traits to have value parameters, added the new syntax for patterns, and allowed patterns to appear in various places including variable declarations, function expressions, functional declarations, and typecase expressions. Changing the syntax involves changing the parser to accept programs with the new concrete syntax, changing the Abstract Syntax Tree (AST) to reflect the new information, and changing the existing interpreter and compiler codes that manipulate the corresponding AST nodes. We implemented the pattern-matching desugarer described in Section 2.3 through Section 2.7 as a compiler phase. When an identifier appears in the context of a pattern, the compiler has to decide whether the identifier denotes a declared type name or a fresh pattern variable. Because the parser cannot make that decision, it generates an ambiguous node for such an identifier and lets a later phase decide whether the node is a type node or a pattern-variable node when more information is available in that phase. Because the information of declared types is available only after the type disambiguator phase, the type disambiguator phase replaces such an ambiguous node with either a type node or a pattern-variable node. Because the expression disambiguator reports static errors for any undefined or shadowed names, the pattern-matching desugarer should desugar the pattern variables away before the expression disambiguator phase. Therefore, we inserted the pattern-matching desugarer between the type disambiguator and the expression disambiguator. As we explained in Section 2.7, the desugaring process sometimes generates identical function declarations; another quick compiler phase right after the pattern-matching desugarer detects such redundant declarations and eliminates them. To support patterns in typecase clauses, we changed the typecase expression syntax and the relevant part of the code. The main modification was in the type-checking phase. Because Fortress is a statically typed language, all expressions get to have types, either by programmer annotations or by the type-inference mechanism in the compiler, and the expression types are used for type checking. The type of a typecase expression is a union of the types of the bodies of all the clauses in the typecase expression. To get the type of a body of each clause, we have to know the types of all the pattern variables used in the body. We determine the types of the pattern variables introduced in a typecase clause using the type of the “binding” expression, which appears between the keywords typecase and of . When the binding expression of a typecase expression has a type τ , we determine the types of the pattern variables in a typecase clause σ according to the following process: 1. When σ is a pattern variable, the pattern variable has type τ . 2. When σ is a type τ 0 prefixed by a pattern variable, the pattern variable has the intersection type of τ and τ 0 . 3. When σ is a type pattern T (π) prefixed by a pattern variable, the pattern variable has the intersection type of T and τ , and the pattern variables in π have the types of the corresponding fields obtained from the definition of T . If a pattern variable in π has a type annotation, it has the intersection type of the annotated type and the corresponding field type. 4. When σ is a tuple pattern prefixed by a pattern variable, the pattern variable has the intersection type of τ and a tuple type

whose element types are the types of the components of the tuple pattern. We determine the types of the tuple pattern components by applying the same process recursively. 5. When σ is a pattern not prefixed by any pattern variable, we apply the same process for the components of the pattern recursively. If any of the intersection types mentioned in the process have disjoint components, such as a tuple type and a non-tuple type, or tuple types of different sizes, the compiler reports a static error. We have not yet fully implemented the Fortress compiler, especially its back end; the front end of the compiler provides a set of various static checks including type checking and the back end generates Java bytecode. While the static checkers handle most of the Fortress language features, the code generator is in a premature stage. We are very close to completing the implementation of the type checker for pattern matching, and the implementation of the back end for pattern matching is work in progress.

4.

Future Work

While this paper describes the main idea of the Fortress patternmatching mechanism, various tasks remain for future work. We are currently working on addressing the issues discussed in this section. Patterns in Fortress do not support value patterns such as 3 , “hello” , or Leaf(4) , which are useful for matching primitive values. For example, the pattern “Leaf(4) ” matches with the value of type Leaf with the value of item , 4 , and the pattern “Node(x, 2, Leaf(4)) ” matches with any values of type Node with the value of item , 2 , and the value of its right subtree, “ Leaf(4) .” Actually, Fortress supports such value matching, similar to a switch statement in Java, by a case expression, but it does not provide patterns. We are planning to extend the Fortress patterns to include value patterns. As we discussed in Section 3, the back end of the Fortress compiler is not yet completely implemented. We are planning to implement code generation of the pattern-matching mechanism presented in this paper. Because we treat most of the patternmatching constructs by rewriting them to Fortress core constructs that do not include any patterns, the increased cost of the compiler back end due to the new pattern-matching mechanism is not a big overhead. The only added task to the code generator is the typecase expression with patterns. We believe that the same optimization techniques used in Scala should apply here. We plan to experiment with them to analyze the performance of the proposed mechanism when the Fortress compiler back end is ready. The Fortress type system signals static errors for redundant cases in typecase expressions. For example, when a binding expression has a non-tuple type but a typecase clause has a tuple type or a tuple pattern, the type checker signals a static error as a redundant or unreachable case. Because the Fortress traits may have comprises clauses, the type checker can infer type hierarchies with “closed” data types. For such type hierarchies, a static checker can verify the exhaustiveness of typecase clauses and the coverage of data types. We are currently implementing such a type-coverage analysis for both the pattern-matching mechanism and the static checking of overloaded functional declarations [4]. While developing Fortress core libraries, the library writers asked for locally bound type variables, such as the following: x : Object = Node(Leaf(3), 5, Leaf(7)) typecase x of t : NodeJT K(l, i, r) where JT K ⇒ f (l, r) else⇒ 42 end

where the type variable T is bound only in the first clause of the typecase expression. While the above example is admittedly contrived, the Fortress library can implement interesting optimizations using such locally bound type variables because the Fortress runtime system preserves type arguments to distinguish data values of different element types at run time. We are working on extending the typecase expressions to support locally bound type variables. Finally, we are working on formalizing the proposed patternmatching mechanism to show how to extend an existing objectoriented language with pattern matching in a rigorous way. We plan to show how we extend the Basic Core Fortress (BCF) presented in the Fortress language specification [3] to the Core Fortress with Pattern Matching (CFPM) systematically. With the calculi, we are proving the type soundness of them and the correctness of the pattern-matching desugaring mechanism, which rewrites a program in CFPM to a corresponding one in BCF.

5.

Related Work

The pattern-matching facilities of Haskell 98 [17] (see also the recent Haskell 2010 Language Report [5]) are well known. Pattern matching in lambda abstractions, function definitions, pattern bindings, list comprehensions, and do expressions is explained formally by desugaring to case expressions, and pattern matching in case expressions is then explained by desugaring to simpler expressions (typically in terms of let , case , if , and lambda expressions) that do not use pattern matching. Haskell allows patterns to be accompanied by Boolean-valued guard expressions; a pattern does not match unless the value of its associated guard expression (which can refer to variables bound by the pattern) is true. Syme et al [19] present a lightweight extension of a .NET language, F# [18], with patterns, which allows powerful pattern matching and is not limited to F# but can be applied to any objectoriented languages. F# provides “active patterns,” similar to extractors in Scala, to define arbitrary patterns for any types. In Fortress, the declarations of traits and objects determine their patterns, but active patterns do not require any specific relationship between the types and their patterns. A single type may have multiple definitions of active patterns, so that the type may have multiple patterns. This representation independence certainly hides the implementation details of data types and user-defined active patterns provide very flexible ways to define and use patterns. However, it has the cost for programmers to define active patterns by themselves, and whenever a programmer wants to access a new field the active pattern should be adjusted, while the Fortress pattern matching concisely allows it by keyword pattern parameters. Finally, F#, SML [14], and OCaml [12] provide more expressive patterns than Fortress, such as or-patterns and and-patterns. Emir, Odersky, and Williams [7, 6] compare (and provide performance evaluations of) six different styles of programming for identifying patterns of objects. While they refer to all six as “pattern-matching techniques”, only the last two (features introduced in Scala) provide what we would call a pattern (sub)language that visually portrays the structure to be identified; the other four styles are rather what might be called “programming patterns” in the vein of the famous Gang of Four [8], that is, idiomatic ways of using existing language features to explicitly program the detection of desired object relationships. Their categorization and comparison is very useful but we wish to draw attention to the fact that what they call “pattern matching with typecase ” actually uses typecase in a very limited way, and does not introduce a pattern sublanguage into typecase clauses in the manner that we have described for Fortress. Scala [7] has introduced pattern matching mechanisms based on case classes and extractors. We have found these mechanisms quite practical and have used them to implement large parts of our

Fortress compiler; indeed, it was primarily this experience with Scala that convinced us to design a pattern-matching facility for Fortress. We feel that the Fortress design improves on Scala in a few important ways: • As Burak Emir et al. [7, 6] observe, “Pattern matching in Scala

is loosely typed, in the sense that the type of a pattern does not restrict the set of legal types of the corresponding selector values.” This is a necessary property of Scala, because it rests on a type system (that of Java) that uses type erasure. Because Fortress does not use erased types, pattern matching does not lose type parameter information and is completely type sound. • Scala introduces certain unfortunate syntactic restrictions: “To

distinguish variable patterns from named constants, we require that variables start with a lower-case letter whereas constants should start with an upper-case letter or special symbol.” [7] There are workarounds for these restrictions, but they are one more thing for the programmer to have to remember. In Fortress, the same name can refer to a singleton object as a value and also to its type, which covers one case that the Scala design addresses; on the other hand, the design we describe is purely type-oriented, and makes no provision for matching object values that are not also types. (Our design also does not provide for Boolean guard expressions in patterns, such as are provided in Haskell [17].) The earlier work of Gostanza et al. [16] deserves mention for introducing the notion of active patterns, which are quite similar to Scala extractors. We agree with the assessment of Emir, Odersky, and Williams [7] that this earlier work, conducted in the context of traditional algebraic data types, does not provide data type injection nor type-parametric inheritance. Martin Hirzel et al. [10] introduced an extension called Matchete for allowing pattern matching in the Java language. The Matchete compiler translates new syntax code for pattern matching to Java source code, making it possible to use Java type checking. While Matchete provides various built-in patterns, such as regular expression patterns, XPath patterns, bit-level patterns, and parameterized patterns, similar to the one in F#, which may be useful for many applications, to use type structured patterns, as used in Fortress, it needs an user-defined method, called deconstructor, within the matching type. This gives a representation independence from client codes and an ability to add patterns for some types, but whenever we add new classes or want to extract a field other than the ones specified in the deconstructor method, we have to add or redefine the deconstructor method. And unlike pattern matching in Fortress, the Matchete compiler does not provide mechanisms to check for completeness of clauses and unreachable clauses in pattern matching. The HydroJ language [11] adds pattern matching to the Java language by introducing a new kind of data called semi-structured data that consists of tagged trees: each tree node has a tag name and an ordered list of children, each of which may be either a tagged tree or one of a specific set of base types (such as integers and strings). The pattern-matching language is very flexible; in particular, the children of each tree node may be matched by a multiset of child patterns that may be either ordered (a list) or unordered (a bag), and (Kleene) star patterns are permitted as well that can match any number of children rather than just one. Something like the effect of Fortress keyword patterns can be obtained simply by using additional tagged nodes to wrap child values, which is a clever and conceptually economical mechanism. However, HydroJ patterns match only these special tagged trees (which, as the authors explain, are intended to serve a special role in providing a flexible evolvable interface among software components); HydroJ apparently provides no facility for matching

ordinary Java objects. (The authors also comment that HydroJ is an embedding of “pure Hydro” into Java for the purpose of conducting practical experiments. JMatch [13] adds pattern matching to the Java language by exploiting modal evaluation: certain expressions can be evaluated either in forward mode, to produce a result, or backward mode, in which, for a given result, values of certain variables in the expression must be deduced. This is reminiscent of, but somewhat more constrained than, a logic programming language such as Prolog. An advantage is that a single expression can be used to define both a constructor and a destructuring pattern (or, as the authors demonstrate in a more complex example, a membership test and an iterator); compare this to the need in Scala to define apply and unapply methods separately. The style of pattern declaration which we describe here also has the advantage that a single declaration can define both a constructor and a pattern, though this arrangement is considerably less programmable than that of JMatch. (On the other hand, it should be noted that Fortress getter methods do allow for a certain amount of programmability that ties into the pattern-matching mechanism.) OMeta [20] is “an object-oriented language for pattern matching,” which is rather different from “pattern matching for an objectoriented language”; it is very interesting work on building parsers that can operate on streams of structured data (not just characters) and use object-oriented class mechanisms to encapsulate grammar components, but it does not directly address the issue of patternmatching trees or graphs of objects. We mention it only as an example of how the same keywords or buzzwords can be combined in different ways to produce strikingly different research directions.

6.

Conclusion

The pattern-matching mechanism is one of the main features of functional languages, which provides a concise way to manipulate data. Intensive manipulation of data types in standard objectoriented languages often results in long, hard-to-read, error-prone programs. To overcome such limitations of object-oriented languages, we presented a lightweight mechanism to add pattern matching to existing object-oriented languages, in the case of the Fortress programming language. The mechanism provides a concise way of defining and using patterns with modest cost of modifying the existing language. Our work was heavily inspired by Syme et al [19] and Scala [7, 6]. Based on the main idea we described in this paper, we are working on various future directions of the Fortress pattern-matching mechanism. In parallel with the Fortress front-end compiler development, we are implementing the type-coverage checks and exhaustiveness checks in typecase expressions. Along with the Fortress back-end compiler development, we plan to implement code generation of the language constructs using patterns. We are also planning to extend the pattern-matching mechanism to support value patterns and locally bound type variables. Finally, we are working on formalizing the pattern-matching mechanism to prove its type soundness and the correctness of the desugaring process.

County Kildare, Ireland, Ireland, 2002. National University of Ireland. [2] Eric Allen, David Chase, Christine Flood, Victor Luchangco, JanWillem Maessen, Sukyoung Ryu, and Guy L. Steele Jr. Project Fortress Community website. http://www.projectfortress. sun.com. [3] Eric Allen, David Chase, Joe Hallett, Victor Luchangco, JanWillem Maessen, Sukyoung Ryu, Guy L. Steele Jr., and Sam Tobin-Hochstadt. The Fortress Language Specification Version 1.0. http://research.sun.com/projects/plrg/fortress.pdf, March 2008. [4] Eric Allen, J. J. Hallett, Victor Luchangco, Sukyoung Ryu, and Guy L. Steele Jr. Modular multiple dispatch with multiple inheritance. In SAC ’07: Proceedings of the 2007 ACM Symposium on Applied Computing, pages 1117–1121, New York, NY, USA, 2007. ACM. [5] Simon Marlow (editor). Haskell 2010 language report. http: //www.haskell.org/onlinereport/haskell2010/, 2010. [6] Burak Emir. Object-oriented pattern matching. PhD thesis, EPFL, Lausanne, 2007. [7] Burak Emir, Martin Odersky, and John Williams. Matching objects with patterns. In ECOOP 2007 – Object-Oriented Programming, volume 4609 of LNCS, pages 273–298. Springer, 2007. [8] Erich Gamma and Richard Helm and Ralph Johnson and John M. Vlissides. Design Patterns: Elements of Reusable Object-Oriented Software. Addison-Wesley, 1994. [9] James Gosling, Bill Joy, Guy Steele, and Gilad Bracha. The Java Language Specification, Third Edition. Addison-Wesley Longman, Amsterdam, 3 edition, June 2005. [10] Martin Hirzel, Nathaniel Nystrom, Bard Bloom, and Jan Vitek. Matchete: Paths through the pattern matching jungle. In In Practical Aspects of Declarative Languages, 10th International Symposium PADL, pages 150–166, 2008. [11] Keunwoo Lee, Anthony LaMarca, and Craig Chambers. Hydroj: object-oriented pattern matching for evolvable distributed systems. SIGPLAN Not., 38(11):205–223, 2003. [12] Xavier Leroy, Damien Doligez, Jacques Garrigue, Didier R´emy, and J´erˆome Vouillon. The Objective Caml System, release 3.08. http:// caml.inria.fr/distrib/ocaml-3.08/ocaml-3.08-refman. pdf, 2004. [13] Jed Liu and Andrew Myers. Jmatch: Iterable abstract pattern matching for java. In Veronica Dahl and Philip Wadler, editors, Practical Aspects of Declarative Languages, volume 2562 of Lecture Notes in Computer Science, pages 110–127. Springer Berlin / Heidelberg, 2003. [14] Robin Milner, Mads Tofte, Robert Harper, and David MacQueen. The Definition of Standard ML (Revised). The MIT Press, 1997. [15] Martin Odersky. The Scala Language Specification, Version 2.7. EPFL Lausanne, Switzerland, 2009. [16] Pedro Palao Gostanza, Ricardo Pe na, and Manuel N´u nez. A new look at pattern matching in abstract data types. In ICFP ’96: Proceedings of the first ACM SIGPLAN international conference on Functional programming, pages 110–121, New York, NY, USA, 1996. ACM.

Acknowledgments

[17] Simon Peyton-Jones. Haskell 98 Language and Libraries. Cambridge University Press, 2003.

The authors sincerely thank the Fortress team for fruitful discussions and support.

[18] Don Syme, Adam Granicz, and Antonio Cisternino. Expert F# (Expert’s Voice in .Net). Apress, 2007.

References

[19] Don Syme, Gregory Neverov, and James Margetson. Extensible pattern matching via a lightweight language extension. SIGPLAN Not., 42(9):29–40, 2007.

[1] Eric Allen and Robert Cartwright. The case for run-time types in generic java. In PPPJ ’02/IRE ’02: Proceedings of the inaugural conference on the Principles and Practice of programming, 2002 and Proceedings of the second workshop on Intermediate representation engineering for virtual machines, 2002, pages 19–24, Maynooth,

[20] Alessandro Warth and Ian Piumarta. Ometa: an object-oriented language for pattern matching. In DLS ’07: Proceedings of the 2007 symposium on Dynamic languages, pages 11–19, New York, NY, USA, 2007. ACM.

Suggest Documents