The state of Swift scripting
In the past few days I’ve spend some time writing a little script in Swift and I thought it was a good time to revisit the state of the Swift ecosystem as a scripting language.
You know, for me, one of the most interesting aspects of Swift is that it claims to be a language that can be used at any level, from scripts and Apps to operating systems. So taking a look at how, in reality, it performs in this camp is important.
It is not like I write scripts every day so I’m sure others have different opinions but I think it is worth revisiting this. I’ve found myself struggling in the past with it, in the meantime writing scripts with other established languages seems more streamlined.
Just to have an idea, the script has the usual requirements of supporting different commands with different options, reading and writing data to disk and interacting with a web API.
It is not the first time I have used Swift for this. Last year I wrote srly.swift which was especially interesting since I wrote it in Ruby first. Even before that I had a small set of helpers to write scripts (that I’ve not updated at all).
Some of the things have improved since then, but sadly, not as much as I would like. The interesting part is that the problems are not intrinsic with the language, but with the tooling and the fact that is still a young community.
When I was preparing to write this script I stopped for a second and thought about what I need from a language and its tooling when I want to write a script. I came up with these points, some are more important than others, and maybe it’s only me that found them important.
- Simple setup
- Facility to use third party code
- Facility to reuse my own code
- Easy distribution
This is applicable to any language that pretends to allow you to write scripts. Let’s dissect each point and give a status on what the reality is with Swift.
This is one constant that is reflected in the rest of the points. Any of the problems that scripting in Swift has can be solved with installing more tools, but this is a thing that I would like to avoid. Especially since, as I said, one doesn’t write scripts everyday, so the last thing I want is to have to manage tooling when I want to write a quick script.
The whole point here is that the time from having a necessity to having something that solves it has to be quick. Otherwise we would be writing a full app for that. If the setup is tedious one doesn’t even want to think about writing the script. And this is a priority that should guide any aspect of using a language for scripting.
This point is probably not important for everyone. I’ve always seen people writing scripts without any help from an IDE. And one can definitely do it in Swift, especially with the upcoming change on naming conventions. I’m a lot more comfortable with autocompletion though, especially if it has a fuzzy matcher that works.
And it isn’t just about autocompletion, it is about exploring the API easily. Being able to type two letters and have a full method with placeholders for where to put parameters is cool, but what is important here is receiving suggestions from the IDE on which methods are available, with all the type information to easily pick the one you were looking for.
Apart from that, using an IDE is becoming almost a necessity. This has always been one of my concerns with type inference. I agree that it is really nice to not have to repeat the types over and over, but when you have to read the code without any help (basic editor, GitHub, web posts, or ANYTHING outside an IDE) type inference doesn’t help at all.
With all of this in mind Xcode seems to be the only contender here. And that is fine for me, I really like it. But there are a couple of issues.
The main one is that Xcode doesn’t do any IDE job without a project. If you just open the script file, forget about having all the nice things I’m talking about.
Apart from that, is a pain in the ass that it only supports the hashbang on files named
main.swift. It is not bad by itself, but it becomes a problem when you plan to share the script as you have to rename it to something useful every time.
Facility to use third party code
If your script becomes anything more than a simple print with some calculations you will definitely need to use third party code. This is the holy grail of other communities (Gems, npm, etc). But the important part is not the number of libraries or how easy it’s to find them, but how easy it’s to use them.
In Swift finding them is not an issue. Usually a quick search on GitHub gives you good results, but we have CocoaPods that is splendid for this. The issue is how to use those libraries.
When writing a script you don’t want to spend time dealing with Xcode projects, workspaces, integrating frameworks or setting up linker flags. You just want to
install from the terminal and
import in your file. That is how easy it should be.
This is something I hope SPM fixes soon. As it seems that the future of the Swift community is going in that direction.
For now the easiest solution that I found is to use Rome, a CocoaPods plugin, made by the awesome Boris, that builds the frameworks and leaves them in a folder so you can link them at runtime from your script adding
-F Rome to the hashbang.
#!/usr/bin/env swift -F Rome
This is fine, but I hope it’s just for now and we have something better in the future.
As a side note, Boris also has built cato and cathode. With them you can just
import a library in the script and it will automatically fetch the dependencies and compile them when the script is executed. This is amazing, but I haven’t used it. As I said, being easy to start is crucial, and having to install more and more stuff just to write a script in Swift diminishes the point of using it.
Facility to reuse my own code
This is probably the least of my concerns, but it’s still something that I would like to see improved. As far as I know, there is no way to run a script and link another source file at runtime. If there is, you know where to send me the answer ;)
The thing is that when writing scripts you don't usually need to pull out a lot of code, usually the parts that are reused are already in their own frameworks. But still, sometimes I want to separate some parts of the script when it starts to get too big.
This is a crucial part. The end step. When you have the script working you don't want to spend too much time figuring out what the best way is to share it with your coworkers or your friends.
It also ties directly to the first point, easy to setup. Being a script means that the machine that runs it has to have all the required set of tools as when it was being developed. So you want to minimise the setup to make distribution easy.
Hopefully all the required tools come from the language toolset, and hopefully it is already included with the operating system. This is something that I expect to see from swift.org and Apple, including the language, core libraries and SPM directly in OSX.
My current solution
All of this shows how hard it’s to do and distribute simple things, even with modern tools. As I already said on my On teaching C post, the biggest problem when developing software is all the (painful) steps we have to go through until everything is ready for us to sit down and fix the actual problem.
With all of this in mind, I have a setup that works. I’m using Xcode (with a project) for writing the script, with the hashbang which means that I have to name it main.swift and I use Rome to import external frameworks. One important thing is to run the script from the command line, to ensure it doesn’t have any hidden dependencies with a project configuration.
A nice thing about this is that it allows me to easily run it form Xcode in case I need to debug something. It’s really amazing to be able to just use lldb while scripting.
For the Xcode project I’m using Swift Command Line Application Template. I started using the command line template that is provided with Xcode, but I had some problems when running the script from Xcode as the linker couldn’t find the frameworks.
This templates also allows you to archive the script as an .app that includes the compiled binary of the script and a folder with the required frameworks. You still have to go inside the .app and grab the binary and the frameworks from there, but at least is a way of distributing it without forcing the user to install any other tools.
This is already too long, so I will leave the talk about actually writing the script for the next post.