Ch.7 SML# feature: record polymorphism

§ 7.7. Polymorphic variants

Records are labeled collection of component values. There is a dual concept of this structure, namely labeled variants. This may not be familiar to many of you, but the essential ingredients are the same as those of records, so in a system where polymorphic records are supported, polymorphic variants can also be represented. For a type theoretical account, see [8]. In this section, we briefly introduce them through simple examples.

Once you understand records as labeled collections of values, you can think of labeled variants as a value attached with a service request label in a context where a labeled collection of services are defined. For a simple example, let us consider a system where we have two point representations, one in Cartesian coordinates and the other in polar coordinates. In this system, each representation is processed differently, so each point data is attached a label indicating its representation. Polymorphic variant is a mechanisms to those data with polymorphic functions. By regarding the data label as a service selector from a given set of services, this system can be represented by polymorphic records. For example, the following show two representations of the same point.

# val myCPoint = fn M => #CPoint M {X=1.0, Y = 1.0};
val myCPoint = fn : ['a#{CPoint: {X: real, Y: real} -> 'b}, 'b. 'a -> 'b]
# val myPPoint = fn M => #PPoint M {r=1.41421356, theta = 45.0 };
val myPPoint = fn : ['a#{PPoint: {r: real, theta: real} -> 'b}, 'b. 'a -> 'b]

A variant data with a tag T is considered as an object that receives a method suits, select appropriate suit using T as the selector, and applies the selected method to itself. One you understand this idea, then all you have to do is to write up the set of necessary methods for each tag. For example, a set of methods to compute the distance from the origin of coordinates can be coded as below.

val distance =
  CPoint = fn x,y,... => Real.Math.sqrt (x *x + y* y),
  PPoint = fn r, theta,... => r

This method suit is invoked by applying an variant object to it.

# myCPoint distance ;
val it = 1.41421356 : real
# myPPoint distance ;
val it = 1.41421356 : real

In this way, various heterogeneous collections can be processed in type safe way.