8.5 Record programming examples
In a language supporting record polymorphic (currently SML# seems to be the only one), one can write generic code by focusing only on relevant properties of problems. This provide a powerful tool for modular construction of programs that scales to large software development.
To understand a flavor of this feature, let us consider a problem to simulate object movement in a parabolic path in a Cartesian coordinate system. An object can be represented as a record containing X:real and Y:real fields representing the current position vector, and Vx:real and Vy:real fields representing the current velocity vector. An object may have a lot of other properties, but for writing a program to simulate objects movement, these attributes are sufficient. Object movement is simulated by repeatedly applying a function move that moves an object from the current location to the location after one unit of time.
val move : [’a#{X:real, Y:real, Vx:real, Vy:real}. ’a * real -> ’a]
In this exercise, we assume that unit of time is 1 second. In the Cartesian coordinate system, since a position vector and a velocity vector are compositions of those of the two coordinates, we can write write functions on X and Y coordinates independently and compose them. The next position is obtained by adding the velocity. So we can write functions on X and Y independently as below (which shows the code and its typing in an interactive session).
# fun moveX (p as {X:real, Vx:real,...}, t:real) = p # {X = X + Vx};
val moveX = fn : [’a#{Vx: real, X: real}. ’a * real -> ’a]
# fun moveY (p as {Y:real, Vy:real,...}, t:real) = p # {Y = Y + Vy};
val moveY = fn : [’a#{Vy: real, Y: real}. ’a * real -> ’a]
Next, we need to write functions that change velocities. Here we assume that on X coordinate, objects maintain its uniform motion, and on Y, objects are uniformly accelerated by gravity. Then acceleration functions can be code as follows.
# fun accelerateX (p as {Vx:real,...}, t:real) = p;
val accelerateX = fn : [’a#{Vx: real}. ’a * real -> ’a]
# fun accelerateY (p as {Vy:real,...}, t:real) = p # {Vy = Vy + 9.8};
val accelerateY = fn : [’a#{Vy: real}. ’a * real -> ’a]
accelerateX is constant and therefore not needed in this simple case, but we write one for future refinement.
The function next can be obtained by composing all of them below.
fun move (p, t) =
let
val p = accelerateX (p, t)
val p = accelerateY (p, t)
val p = moveX (p, t)
val p = moveY (p, t)
in
p
end
The resulting code is highly modular and type safe, and can be easily refined. For example, if we want to add deacceleration on X coordinate by 1% per second, one need only re-write accelerateY to the following.
# fun accelerateX (p as {Vx:real,...}, t:real) = p # {Vx = Vx * 0.90};
val accelerateX = fn : [’a#{Vx: real}. ’a * real -> ’a]
For this next function, SML# infers the type we designed at the beginning of this section, and therefore can be applied to any object having many other attributes.