Categories
Tech

Scala Saturday – Code That Looks Like Math

Something that Scala and most other modern languages allow these days is variable names that contain what you might think of as non-traditional characters from the Unicode character set, e.g., Greek symbols such as π and τ. If you’re a C programmer, you have to settle for spelling out the name of the character:

const double PI = 3.141592654;
double delta = x1 - x2;

But that’s OK, right? What’s the difference, really? The value π is one thing: it’s a universally recognized constant. But even with the example delta above, don’t you want to name it something more descriptive, like marginOfError anyway?

Well, yes, many times instead of using the characters verbatim from your physics textbook …

val f = m * a

… you spell it out so that the code is clearer:

val force = mass * acceleration

Likewise, even though Scala allows you to write the following:

val ω = 2 * math.Pi * f

… it’s probably better practice to write …

val angularVelocity = 2 * math.Pi * frequency

What’s the point of this post then? Sure, you can use “special” characters in variable names, but so far, I’ve discouraged you from doing it!

Nevertheless there are times when it is appropriate. If you are coding up an algorithm that consists of a series of well-known equations in a certain field of study, and the more your code looks like those equations, the easier it is to check it against the literature.

Consider the following—a series of values and equations for converting latitude and longitude to universal polar stereographic (UPS) coordinates, a way of representing coordinates at the earth’s poles:

Values and equations for converting geodetic coordinates (latitude and longitude) into universal polar stereographic (UPS) coordinates
Converting Latitude and Longitude to Universal Polar Stereographic (UPS) Coordinates

UPS coordinates consist of a hemisphere—either northern or southern—and two distance components, easting and northing, both in meters:

object Hemisphere extends Enumeration {
  type Hemisphere = Value

  val Northern = Value('N')
  val Southern = Value('S')

  def fromLatitude(lat: Double): Hemisphere =
    if (lat < 0) Southern else Northern
}

case class UniversalPolarStereographic(
    northing: Double,
    easting: Double,
    hemisphere: Hemisphere)

Now compare the code below to the equations from the literature above:

def latLonToUps(
    lat: Double, 
    lon: Double): UniversalPolarStereographic = {

  val hemisphere = Hemisphere.fromLatitude(lat)

  val φ = lat.abs
  val λ = lon
    
  val π = math.Pi

  val FN = 2000000.0
  val FE = 2000000.0

  val a = 6378137.0
  val f = 1 / 298.257223563

  val e_2 = f * (2 - f)
  val e = math.sqrt(e_2)
  val eOver2 = e / 2
  val Câ‚’ = ((2 * a) / math.sqrt(1 - e_2)) *
           math.pow((1 - e) / (1 + e), eOver2)
  val kâ‚’ = 0.994
  val πOver4 = π / 4

  val esinφ = e * math.sin(φ)
  val φOver2 = φ / 2

  val tanZOver2 = 
    math.pow((1 + esinφ) / (1 - esinφ), eOver2) *
    math.tan(πOver4 - φOver2)
  val R = kâ‚’ * Câ‚’ * tanZOver2
  val Rcosλ = R * math.cos(λ)
  val Rsinλ = R * math.sin(λ)

  val N = hemisphere match {
    case Hemisphere.Northern => FN - Rcosλ
    case Hemisphere.Southern => FN + Rcosλ
  }
  val E = FE + Rsinλ

  UniversalPolarStereographic(N, E, hemisphere)
}

It’s not perfect: you still cannot set numerators above denominators, for instance. But isn’t that easier to compare to the literature than if we had to write out RsinLambda or eSinPhi?

(Note: UPS coordinates are only valid for latitudes near the poles. For simplicity, the code above does not check to make sure that the input latitude falls within those bounds. I mean, it’s complex enough as it is for the sake of exemplifying the point of this post.)

(Note: I’m aware that some of the characters in the code don’t show up correctly on all browsers, e.g., the subscript “O” and perhaps the φ. I’m working to correct that. Nevertheless you should be able to use such symbols in your source code.)

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.