Hi everyone,
I’d like to share an idea to help provide a new way of exploring, exercising and playing with code in Swift. Inspired by the Playgrounds features in Xcode and the Swift Playground app – and similar features for other languages – I wanted to build the same kind of lightweight code exploration experience natively into Swift and Swift tools everywhere. This post describes my prototype of the idea.
The prototype consists of:
- A new
#Playground
macro that makes it easy to define runnable blocks of code in any Swift file, with access to private entities in the file. - A new Swift package sub-command
swift play
that can be used to enumerate and run playgrounds found in a package, providing a live coding environment by automatically re-running on any source changes. - Tools-specific Playgrounds API for any tool or IDE to add support for finding and running
#Playground
instances (as used byswift play
).
For example, let’s say you’re working on a function to calculate fibonacci sequences, as part of a larger library. Before running the project or even building tests you could use a #Playground
to quickly execute just the fibonacci function directly, helping you to rapidly iterate on the implementation with a focused workflow.
$ cat Sources/Fibonacci/Fibonacci.swift
func fibonacci(_ n: Int) -> Int {
...
}
import Playgrounds
#Playground("Fibonacci") {
for n in 0..<10 {
print("fibonacci(\(n)) = \(fibonacci(n))")
}
}
You could use swift play
to enumerate playgrounds in the package and run the Fibonacci playground:
$ swift play --list
Building for debugging...
Found 1 Playground:
* Fibonacci/Fibonacci.swift:23 "Fibonacci"
$ swift play Fibonacci
Building for debugging...
---- Running Playground "Fibonacci" - Hit ^C to quit ----
fibonacci(0) = 0
fibonacci(1) = 1
fibonacci(2) = 1
fibonacci(3) = 2
fibonacci(4) = 3
fibonacci(5) = 5
fibonacci(6) = 8
fibonacci(7) = 13
fibonacci(8) = 21
fibonacci(9) = 34
^C
The --list
option can be used to enumerate all playground instances found in the library.
Providing swift play
with a playground identifier (either the name or file/line details) as an argument will run the specified playground.
Note that running doesn't immediately stop at the end of the playground body, requiring ^C to end execution. This is for two really useful reasons:
- Any asynchronous code or callbacks will continue to execute and produce output;
swift play
monitors for any changes to files in the package and will automatically re-build/re-run if a file is modified. This makes for a super convenient iterative development tool.
The new playgrounds functionality consists of the following additions:
- A new Playgrounds library which provides:
- The
#Playground
macro; - API for tools to integrate support for finding and running playground instances. The API is subject to change, but currently looks like:
__Playground.__allPlaygrounds()
– returns a list of all playgrounds found in the process__Playground.__getPlayground(named: playgroundName)
– returns an instance of a playground by name (if one exists)try await playground.__run()
– executes the body of a playground
- The
- The addition of an experimental
swift play
sub-command to swift-package-manager. The first client of the above tools API.
This idea has been prototyped for macOS only initially (with other platforms to be supported soon) in a new package named swift-play-experimental, providing the Playgrounds library & macro. Along with a prototype swift-package-manager branch eng/chrismiles/swift-play-prototype
in my fork, implementing swift play
.
These are currently considered experimental and not yet recommended for general production use. If you’re interested, I encourage you to clone them, explore the implementation, and try using playgrounds in your projects. See CONTRIBUTING for details on how to try out the prototype.
I would love to hear your feedback about these ideas and your experience using the #Playground
and swift-play
prototype implementation.
Cheers!
Chris