#Playground macro and "swift play" idea for code exploration in Swift

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 by swift 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:

  1. Any asynchronous code or callbacks will continue to execute and produce output;
  2. 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 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

39 Likes

I like this idea a lot! I'd argue that this actually has good precedent with SwiftUI's previews - it's

  • A macro
  • which generates an object holding executable code
  • that is picked up by the IDE/runner
  • and can hold multiple "executables" (different functions for yours, different views for Previews)
  • with the goal of making code easy-to-iterate

I feel like this feature would make logic a lot easier to debug, because you don't need to create a test target and tests to "try out" your code individually. The semi-tests are also in the same file as the actual code - for a production function I wouldn't do this, but for figuring things out I think it is very helpful.

However, I'd stay away from actually naming it "Playground(s)". Playgrounds already refers to

  • Swift Playground (app),
  • Swift Playground (runnable project),
  • Xcode Playground (runnable file),
  • App Playground (SPM project with an executable), and
  • Playground (educational games for Swift in the Swift Playground app).

It really doesn't need another meaning

2 Likes

I really like this idea. On macOS, it’s extremely useful to be able to just add a playground file to your project, since it has access to all the regular Swift files in the project and allows you to test code without running the entire app, or test new types that haven’t been integrated into the rest of the app yet. This is something I definitely miss when developing for other platforms.

I do agree with @KaiTheBuilder though that we probably don’t need another overload for the term “Playground”.