Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.
I’ve been on a Swift + R bender for a while now, but have been envious of the pure macOS/iOS (et al) folks who get to use Apple’s seriously ++good machine learning libraries, which are even more robust on the new M1 hardware (it’s cool having hardware components dedicated to improving the performance of built models).
Sure, it’s pretty straightforward to make a command-line utility that can take data input, run them through models, then haul the data back into R, but I figured it was about time that Swift got the “Rust” and “Go” treatment in terms of letting R call compiled Swift code directly. Thankfully, none of this involves using Xcode since it’s one of the world’s worst IDEs.
To play along at home you’ll need macOS and at least the command line tools installed (I don’t think this requires a full Xcode install, but y’all can let me know if it does in the comments). If you can enter swiftc
at a terminal prompt and get back <unknown>:0: error: no input files
then you’re good-to-go.
Hello, Swift!
To keep this post short (since I’ll be adding this entire concept to the SwiftR tome), we’ll be super-focused and just build a shared library we can dynamically load into R. That library will have one function which will be to let us say hello to the planet with a customized greeting.
Make a new directory for this effort (I called mine greetings
) and create a greetings.swift
file with the following contents:
NOTE: Please substitute a real @ sign for the @ used here. One of the extensions that auto-converts @- things to Twitter links is busted and turns them all to Twitter links.
All this code is also in this gist.
@_cdecl("greetings_from") public func greetings_from(_ who: SEXP) -> SEXP { print("Greetings, 🌎, it's \(String(cString: R_CHAR(STRING_ELT(who, 0))))!") return(R_NilValue) }
Before I explain what’s going on there, also create a geetings.h
file with the following contents:
#define USE_RINTERNALS #include <R.h> #include <Rinternals.h> const char* R_CHAR(SEXP x);
In the Swift file, there’s a single function that takes an R SEXP
and converts it into a Swift String
which is then routed to stdout
(not a “great” R idiom, but benign enough for an intro example). Swift functions aren’t C functions and on their own do not adhere to C calling conventions. Unfortunately R’s ability to work with dynamic library code requires such a contract to be in place. Thankfully, the Swift Language Overlords provided us with the ability to instruct the compiler to create library code that will force the calling conventions to be C-like (that’s what the @cdecl
is for).
We’re using SEXP
, some R C-interface functions, and even the C version of NULL
in the Swift code, but we haven’t done anything in the Swift file to tell Swift about the existence of these elements. That’s what the C header file is for (I added the R_CHAR
declaration since complex C macros don’t work in Swift).
Now, all we need to do is make sure the compiler knows about the header file (which is a “bridge” between C and Swift), where the R framework is, and that we want to generate a library vs a binary executable file as we compile the code. Make sure you’re in the same directory as both the .swift
and .h
file and execute the following at a terminal prompt:
swiftc \ -I /Library/Frameworks/R.framework/Headers \ # where the R headers are -F/Library/Frameworks \ # where the R.framework lives -framework R \ # we want to link against the R framework -import-objc-header greetings.h \ # this is our bridging header which will make R C things available to Swift -emit-library \ # we want a library, not an exe greetings.swift # our file!
If all goes well, you should have a libgreetings.dylib
shared library in that directory.
Now, fire up a R console session in that directory and do:
greetings_lib <- dyn.load("libgreetings.dylib")
If there are no errors, the shared library has been loaded into your R session and we can use the function we just made! Let’s wrap it in an R function so we’re not constantly typing .Call(…)
:
greetings_from <- function(who = "me") { invisible(.Call("greetings_from", as.character(who[1]))) }
I also took the opportunity to make sure we are sending a length-1 character vector to the C/Swift function.
Now, say hello!
greetings_from("hrbrmstr")
And you should see:
Greetings, 🌎, it's hrbrmstr!
FIN
We’ll stop there for now, but hopefully this small introduction has shown how straightforward it can be to bridge Swift & R in the other direction.
I’ll have another post that shows how to extend this toy example to use one of Apple’s natural language processing libraries, and may even do one more on how to put all this into a package before I shunt all the individual posts into a few book chapters.
R-bloggers.com offers daily e-mail updates about R news and tutorials about learning R and many other topics. Click here if you're looking to post or find an R/data-science job.
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.