Chapter 3 Building a Real Command Line Tool — Part 1

Technically we already have built a “real” command line tool, but it is fairly useless. The one we’ll be building starting with this chapter will be a tool that we can divine the sunrise and sunset times for a given location on a given date. Now, we could totally do this just in Swift, but there’s a handy and small R package — {daybreak}22 — that has sunrise/sunset compute functions and wrapping it into a command line app will help us ease into task of interfacing with more complex projects.

3.1 A Fresh Start

If you followed the advice in the previous chapter to make an copy/archive/snapshot of the last project (before we made the finished product), follow the advice in this handy article23 and rename the project to ephemerids. Sadly, it’s a Medium platform post, so consider opening the footnoted link in a private browsing window.

Once you’ve renamed and opened the new project the first thing we need to do is poke some holes in the “hardened runtime” — a series of limitations Apple prefers apps to run under to, ostensibly, make your computing environment safer.

Since {daybreak} requires compilation it has a shared library which does not have an Apple-required digital signature that is intended to somewhat assure you code you’re running is not evil. To use it with our compiled Swift+R app we’ll need to disable “library validation” which you can do under the “ephemerids” target’s “Signing & Capabilities”:

With that step performed, we can start the wrapping process.

3.2 Mapping Out Steps To Our Goal

As noted in the introduction to this chapter, we’re building a command line app to let us get sunrise and sunset times for a given place on a given date and we’re going to be using an R package for some of the functionality. That means we will:

  • (at some point) take in command line arguments from the user — we’ll do this in a later chapter to keep this one simple and just hard code inputs for now.
  • need to ensure the {daybreak} package is installed. Since we’re relying on the local R installation, {daybreak} needs to be available for use, so we’ll want to make sure it’s there and prompt the user to install it if isn’t, vs crash and burn with no helpful message saying why.
  • load the {daybreak} package into our embedded R session so we have access to the sun_rise_set function that we’ll be using from the package.
  • call the sun_rise_set function and display output to the user

We’ll also build some utility functions (that we can reuse in other projects) to load R packages and make it a bit easier to work with R SEXPs.

3.3 Gotcha #2: “Complex” C Macros

We’ll be working with strings and one popular way to get a C string from a compatible R SEXP24 is:

CHAR(Rf_asChar(x))

CHAR is a macro defined in Rinternals.h like this:

#define CHAR(x)     ((const char *) STDVEC_DATAPTR(x))

Since we’ll be working with SEXPs we’ll also have to remember to keep any of the ones we make in Swift safe from R’s garbage collector25. That would normally mean making heavy use PROTECT and UNPROTECT macros which are also defined in Rinternals.h:

#define PROTECT(s)  Rf_protect(s)
#define UNPROTECT(n)    Rf_unprotect(n)

Unfortunately, CHAR, PROTECT, and UNPROTECT are deemed “complex C macros” as they aren’t just defining constants. Complex C macros wont work in Swift’s C bridge26, which means we need to work around this in various ways.

We’ll work around the CHAR gotcha in swift-r-brige.c by adding a function wrapper (since we can still use CHAR() on the C side):

// it's likely the compiler would optimize this anyway
// but by using `inline` we're asking it to replace
// a call of `CHAR_Rf_asChar`  with its body, thus avoiding the // overhead of a function call and mimicking what `CHAR` was
// originally doing.
inline const char *CHAR_Rf_asChar(SEXP x) {
  return(CHAR(Rf_asChar(x)));
}

(make sure to put the matching function declaration — sans inline in the .h file).

For PROTECT and UNPROTECT we’ll put one set of Swift-R workaround helpers in a new file called r-utils.swift which should look like this:

import Foundation

let PROTECT = Rf_protect
let UNPROTECT = Rf_unprotect

We’re defining those mainly so we can just use PROTECT and UNPROTECT as we would in C. There are some side-effects of doing this, one of which is that SEXPs protected with this Swift-wrapped PROTECT are going to be treated as Optional (more on Optionals later) values. Another, more major side effect is that if R Core ever changes what those two macros do we’ll need to change or abandon this workaround.

To stick closer to what R Core intended, we’ll define another set on the C side:

inline SEXP RPROTECT(SEXP x) {
  return(PROTECT(x));
}

inline void RUNPROTECT(int x) {
  UNPROTECT(x);
}

(again, make sure to put the matching function declaration — sans inline in the .h file).

Use either of them but try to stick to a consistent use pattern.

3.4 Gotcha #3: C Types (a.k.a. I got dem Int32 blues)

R uses 32-bit integers so anywhere there are ints in R’s C code they’re 32-bit. If we try to use plain Swift Int32 when working with R’s C interface we’ll eventually see something like this:

Cannot convert value of type
'Swift.UnsafeMutablePointer<__ObjC.Int32>' 
(aka 'UnsafeMutablePointer<UInt32>') 
to expected argument type
'Swift.UnsafeMutablePointer<Swift.Int32>'

Swift’s types/objects are not C types. Swift has explicit C-compatible numeric types defined as type aliases:

  • CInt is a C int (also usable explicitly as Swift.Int32)
  • CBool is a C bool
  • CChar is a C char (also usable explicitly as Swift.Int8)
  • CFloat is a C float (also usable explicitly as Swift.Float)

We’ll use the C… types when needed when bridging R’s C code and Swift.

3.5 Requiring Packages

While there are plenty of useful tools in the default global R environment, our daybreak::sun_rise_set() function is not in there yet, so we need some way to do the equivalent of library(daybreak) or require(daybreak). We’ll need to do this for other packages to, so a generic require() Swift function seems like a good utility to build.

3.5.1 Noise Cancellation and Error Handling

Loading packages into R can be a noisy operation since R likes to be helpful and warn you about any potential namespace conflicts and other dependent packages being loaded as it makes a package’s functionality available on search path.

Many packages (unfortunately) also spew messages upon load. None of this verbosity is helpful to users of our command line app so we need to ensure packages are loaded as quietly as possible.

We’ll want to use require() vs library() since we get a helpful TRUE/FALSE value on success vs an error, and we can use this feedback to know whether to prompt the user to install any given package.

Finally, if a package is not available R will provide a helpful warning from require(), so we’ll need to stop that from happening.

So, we’ll need to build the Swift equivalent of:

res = suppressWarnings(
  require(
    package = "daybreak", 
    warn.conflicts = FALSE, 
    quietly = TRUE
  )
)

if (!res) { 
  cat("The {daybreak} package is not available. Please remotes::install_github('hrbrmstr/daybreak')\n")
}

which means:

  • using a pairlist27 to build the require() function call since we’re using named parameters
  • using that call as a parameter to a call to a Swift-wrapped suppressWarnings
  • provide a mechanism for Swift to know about package load failures so we can use this to prompt the user. For this we’ll rely on Swift Errors28.

We’ll start with the easy task first and add an RError enumeration to r-utils.swift:

enum RError: Error {
  case tryEvalError(String)
}

We’re calling it tryEvalError since we’ll be using R_tryEvalSilent() to evaluate the created expressions since it won’t die on error or display error messages. On error it returns NULL and sets a pass-by-reference error parameter to a non-zero value.

We’ll raise (throw) and error via:

throw RError.tryEvalError("Some helpful error message.")

3.5.2 Using defer{} with PROTECT()

Whenever PROTECT is used, e.g. PROTECT(Rf_ScalarLogical(1)) there needs to be a corresponding UNPROTECT(), and this balanced PROTECT/UNPROTECT dance must be complete regardless of any logic branches or error catches in your code.

Swift provides something like R’s own on.exit() capability with defer { }. Anything in a defer block is run after any code return path in a given function. Like on.exit() these are stacked (in reverse).

We can immediately postcede all PROTECT calls with a defer { UNPROTECT(#) } line, (# likely being 1 though that won’t always be the case, e.g. when PROTECTing Rf_allocVector calls). This way they are guaranteed to be unprotected even if we throw an error, and we don’t have to remember to unprotect anything later on. You may still have need to non-defer an UNPROTECT but deferring should work well in most cases.

3.5.3 Our Swift require() Implementation

Add this to r-utils.swift

// Swift helper to require(package)
func require(_ pkg: String) throws -> Bool {
  
  var rErr: CInt = 0 // holds our error return value from evaluation
  
  // setup the call to require
  
  let requireCall: SEXP = PROTECT(Rf_allocVector(SEXPTYPE(LANGSXP), 3+1))!
  defer { UNPROTECT(1) }
  SETCAR(requireCall, Rf_install("require"))
  
  // with params
  
  var reqParams: SEXP = CDR(requireCall);
  SETCAR(reqParams,  PROTECT(Rf_mkString(pkg)))
  defer { UNPROTECT(1) }
  SET_TAG(reqParams, Rf_install("package"))
  
  reqParams = CDR(reqParams)
  SETCAR(reqParams, PROTECT(Rf_ScalarLogical(1)))
  defer { UNPROTECT(1) }
  SET_TAG(reqParams, Rf_install("warn.conflicts"))
  
  reqParams = CDR(reqParams)
  SETCAR(reqParams, PROTECT(Rf_ScalarLogical(1)))
  defer { UNPROTECT(1) }
  SET_TAG(reqParams, Rf_install("quietly"))
  
  // now call ^^ via suppressWarnings
  // requireCall is PROTECTed above but Rf_install() may allocate which could kill it so make sure it is safe
  
  let suppressWarningsCall = PROTECT(Rf_lang2(Rf_install("suppressWarnings"), PROTECT(requireCall)))
  defer { UNPROTECT(2) }
  
  let res: SEXP = PROTECT(R_tryEvalSilent(suppressWarningsCall, R_GlobalEnv, &rErr))!
  defer { UNPROTECT(1) }

  // see exposition below for the error handling
  
  if (rErr == 0) {
    if (SEXPTYPE(TYPEOF(res)) == LGLSXP) {
      return(Rf_asLogical(res) == 1)
    } else {
      throw RError.tryEvalError(
        "R evaluation error attempting to load. Expected TRUE/FALSE, but found '\(String(cString: Rf_type2char(SEXPTYPE(TYPEOF(res)))))"
      )
    }
  } else {
    throw RError.tryEvalError("R evaluation error attempting to load \(pkg): \(String(cString: R_curErrorBuf()))")
  }
  
}

Upon no error result from our R function execution we double check that our return value type is, in fact, a logical. SEXPs can be anything, and even though we kinda know require is returning a logical, R has no other way to communicate that except by checking the type.

3.6 Testing require()

Make main.swift look like this:

import Foundation

initEmbeddedR()

do {
  if try require("daybreak") {
    // eventually do something
  } else {
    print("The {daybreak} package is not available. Please remotes::install_github('hrbrmstr/daybreak')")
  }
} catch {
  debugPrint("Error: \(error)")
}

R_RunExitFinalizers()
R_CleanTempDir()
Rf_endEmbeddedR(0)

and run the product. You should see

The {daybreak} package is not available. Please remotes::install_github('hrbrmstr/daybreak')

if you haven’t installed the package yet and see nothing if you have.

We’re now ready to call functions in our newly available package.

3.7 Bridging SEXPs, Making Time, and Finding Daylight

Before we can see daylight displayed in the terminal we’ll want to wrap a call do daybreak::sun_rise_set() with a Swift function we’ll call sunRiseSet().

The daybreak::sun_rise_set() function takes a string and two doubles as parameters and returns two decimal hour (GMT) values in a list:

sun_rise_set("2021-01-02", -70.8636, 43.2683)
## $rise
## [1] 12.25761
##
## $set
## [1] 21.28906

Every time we take values from Swift to R we need to use functions like Rf_mkString, Rf_asInteger, Rf_asReal, etc. — sometimes with PROTECTion — and every time we take values from R to Swift we need to use corresponding CHAR_Rf_asChar (the one we made to wrap the usual way of getting C strings back), Rf_ScalarInteger, Rf_ScalarReal, etc. and often put them into a Swift numeric object (e.g. String, Int, Double).

These are, frankly, kinda ugly and make code far more verbose than it really needs to be. Let’s build some extensions29 to String, Int, and Double to give these value machinations a face-lift.

For each of these objects, we’ll want three extensions:

  • init?(_ sexp: SEXP) {} which will take a SEXP as an object creation input and — after ensuring it’s length == 1 and is a compatible type create a compatible Swift object or return nil (the ? after init signals this object initializer may not always work and could return an Optional30).
  • var SEXP: SEXP { } which is a computed property31 that will let us do things like 12.SEXP and get back an INTSXP.
  • var protectedSEXP: SEXP {} which returns a PROTECTed version of the given SEXP.

Create a new Swift file called sexp-utils.swift and make it look like:

import Foundation

extension String {
  init?(_ sexp: SEXP) {
    if ((sexp != R_NilValue) && (Rf_length(sexp) == 1)) {
      switch (TYPEOF(sexp)) {
      case STRSXP: self = String(cString: CHAR_Rf_asChar(sexp))
      default: return(nil)
      }
    } else {
      return(nil)
    }
  }
  var SEXP: SEXP { return(Rf_mkString(self)) }
  var protectedSEXP: SEXP { return(RPROTECT(Rf_mkString(self))) }
}

extension Int {
  init?(_ sexp: SEXP) {
    if ((sexp != R_NilValue) && (Rf_length(sexp) == 1)) {
      switch (TYPEOF(sexp)) {
      case INTSXP: self = Int(Rf_asInteger(sexp))
      default: return(nil)
      }
    } else {
      return(nil)
    }
  }
  var SEXP: SEXP { return(Rf_ScalarInteger(CInt(self))) }
  var protectedSEXP: SEXP { return(RPROTECT(Rf_ScalarInteger(CInt(self)))) }
}

extension Double {
  
  init?(_ sexp: SEXP) {
  
    if ((sexp != R_NilValue) && (Rf_length(sexp) == 1)) {
      switch (TYPEOF(sexp)) {
      case REALSXP: self = Rf_asReal(sexp)
      case INTSXP: self = Double(Int(sexp)!) // a tad more dangerous
      case STRSXP: self = Double(String(sexp)!)! // a tad more dangerous
      default: return(nil)
      }
    } else {
      return(nil)
    }
    
  }
  
  var SEXP: SEXP { return(Rf_ScalarReal(self)) }
  var protectedSEXP: SEXP { return(RPROTECT(Rf_ScalarReal(self))) }
  
  }
}

(Note the use of RPROTECT in the above to avoid having to declare the SEXP as optional or force-unwrap an Optional like we would if we had just used PROTECT. We could have just used Rf_protect() but this is ultimately safer.)

At the beginning of this section we noted that sun_rise_set returns a decimal hour. Those aren’t very useful to humans so we’ll need to convert them to HH:MM.

We’ll also noted that we’ll be writing a Swift function to wrap the R function and we’ll definitely want to have sunRiseSet() use an actual Date as input for type safety and communicating explicit intent.

Let’s extend Date to make it easier to get an ISO8601 date string32 and also extend Double to make it easier to process decimal dates and use the bits we’ll add to Date to get the string we’re looking for right from a Decimal date.

Create another file called utils.swift and make it look like:

import Foundation

extension Date {
  
  init(fromISODate: String) {
    self = Date.iso8601Formatter.date(from: fromISODate)!
  }
  
  var ISODate: String {
    Date.iso8601Formatter.string(from: self)
  }
  
  static let iso8601Formatter: ISO8601DateFormatter = {
    let fmt = ISO8601DateFormatter()
    fmt.formatOptions = [ .withFullDate, .withDashSeparatorInDate ]
    return(fmt)
  }()
  
}

extension Double {
  
  var whole: Self { rounded(.toNearestOrAwayFromZero) }
  var fraction: Self { truncatingRemainder(dividingBy: 1) }
  
  var decimalTimeToHM: String {
    return(String(format: "%02d:%02d", Int(whole), Int(fraction*60)))
  }

}

We’re now ready to build our sunRiseSet() function.

Create one, final file: sunriseset.swift.

Our function will return two values, so we will need a struct that, so start by making sunriseset.swift look like this:

import Foundation

struct sunriset {
  let rise: Double
  let set: Double
}

Now, we need to build a call to sun_rise_set using Rf_lang4() which takes a callable expression and 3 unnamed parameters and use VECTOR_ELT() to get the values of each returned list element.

While lat.protectedSEXP is not only a tad less verbose than the equivalent PROTECT(Rf_ScalarReal(lat)), PROTECT(Rf_ScalarInteger(CInt(self))) starts to get a little more gnarly than the Int equivalent (which isn’t being used here) and having the details of the date-to-String-to-SEXP conversion hidden away is absolutely much nicer than polluting this, now, simple function call with all that code.

func sunRiseSet(date: Date, lng: Double, lat: Double) -> sunriset {
  
  var err: CInt = 0

  let call_ƒ = RPROTECT(
    Rf_lang4(
      Rf_install("sun_rise_set"),
      date.ISODate.protectedSEXP,
      lng.protectedSEXP,
      lat.protectedSEXP
    )
  )
  defer { UNPROTECT(4) }

  let res: SEXP = PROTECT(R_tryEvalSilent(call_ƒ, R_GlobalEnv, &err)) ?? R_NilValue
  defer { UNPROTECT(1) }

  return(sunriset(
    rise: Double(VECTOR_ELT(res, 0)) ?? -1,
     set: Double(VECTOR_ELT(res, 1)) ?? -1
  ))

}

3.8 The main() Event

Since we’re hard coding values in this chapter, our main.swift is pretty light:

import Foundation

initEmbeddedR()

do {
  if try require("daybreak") {
    let res = sunRiseSet(date: Date(), lng: -70.8636, lat: 43.2683)
    print("Sunrise: \(res.rise.decimalTimeToHM) GMT\n Sunset: \(res.set.decimalTimeToHM) GMT")
  } else {
    print("The {daybreak} package is not available. Please remotes::install_github('hrbrmstr/daybreak')")
  }
} catch {
  print("Error: \(error)")
}

R_RunExitFinalizers()
R_CleanTempDir()
Rf_endEmbeddedR(0)

The decimalTimeToHM extension to Double makes quick work of the printing.

Running the above should produce:

Sunrise: 12:15 GMT
 Sunset: 21:20 GMT

3.9 Up Next

We’ll modify this project to take arguments from the command line in the next chapter.

Before diving in, try wrapping the other functions of the {daybreak} package.

Remember, code examples can be found on GitHub33.