REPL Support for Swift Packages

The swift run command has a new --repl option which launches the Swift REPL with support for importing library targets of a package.

The Swift distribution comes with a REPL for the Swift language. The Swift REPL is a great tool for experimenting with Swift code without needing to create a throwaway Swift package or Xcode project. The REPL can be launched by running the swift command without any arguments.

The Swift REPL allows you to import the core libraries like Foundation, Dispatch and system modules like Darwin on macOS and Glibc on Linux. In fact, the REPL allows you to import any Swift module as long as it can correctly find and load them using the compiler arguments that are provided while launching the REPL. Swift Package Manager leverages this feature and launches the REPL with the compiler arguments that are required for importing library targets of a package.

Examples

Let’s explore the new functionality using some examples:

Yams

Yams is a Swift package for working with YAML.

Clone the package and launch REPL using swift run --repl:

$ git clone https://github.com/jpsim/Yams
$ cd Yams
$ swift run --repl

This should compile the package and launch the Swift REPL. Let’s try using the dump method which converts an object to YAML:

  1> import Yams

  2> let yaml = try Yams.dump(object: ["foo": [1, 2, 3, 4], "bar": 3])
yaml: String = "bar: 3\nfoo:\n- 1\n- 2\n- 3\n- 4\n"

  3> print(yaml)
bar: 3
foo:
- 1
- 2
- 3
- 4

Similarly, we can use the load method to convert the string back into an object:

  4> let object = try Yams.load(yaml: yaml)
object: Any? = 2 key/value pairs {
  ...
}

  5> print(object)
Optional([AnyHashable("bar"): 3, AnyHashable("foo"): [1, 2, 3, 4]])

Vapor’s HTTP

The Vapor project has a HTTP package built on top of SwiftNIO package.

Clone the package and launch REPL using swift run --repl:

$ git clone https://github.com/vapor/http
$ cd http
$ swift run --repl

Let’s make a GET request using the HTTPClient type:

  1> import HTTP
  2> let worker = MultiThreadedEventLoopGroup(numberOfThreads: 1)
  3> let client = HTTPClient.connect(hostname: "httpbin.org", on: worker).wait()
  4> let httpReq = HTTPRequest(method: .GET, url: "/json")
  5> let httpRes = try client.send(httpReq).wait()

  6> print(httpRes)
HTTP/1.1 200 OK
Connection: keep-alive
Server: gunicorn/19.9.0
Date: Sun, 30 Sep 2018 21:30:41 GMT
Content-Type: application/json
Content-Length: 429
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
Via: 1.1 vegur
{
  "slideshow": {
    "author": "Yours Truly",
    "date": "date of publication",
    "slides": [
      {
        "title": "Wake up to WonderWidgets!",
        "type": "all"
      },
      {
        "items": [
          "Why <em>WonderWidgets</em> are great",
          "Who <em>buys</em> WonderWidgets"
        ],
        "title": "Overview",
        "type": "all"
      }
    ],
    "title": "Sample Slide Show"
  }
}

We can use Foundation’s JSONSerialization to parse the response:

  7> let result = try JSONSerialization.jsonObject(with: httpRes.body.data!) as! NSDictionary
result: NSDictionary = 1 key/value pair {
  [0] = {
    key = "slideshow"
    value = 4 key/value pairs {
      [0] = {
        key = "slides"
        value = 2 elements
      }
      [1] = {
        key = "author"
        value = "Yours Truly"
      }
      [2] = {
        key = "title"
        value = "Sample Slide Show"
      }
      [3] = {
        key = "date"
        value = "date of publication"
      }
    }
  }
}

Implementation Details

Using the REPL with a Swift package requires two pieces of information in order to construct the REPL arguments. The first piece is providing the header search paths for the library targets and their dependencies. For Swift targets, this means providing the path to the module’s .swiftmodule file and for C targets, we need the path of the directory containing the target’s modulemap file. The second piece is constructing a shared dynamic library that contains all of the library targets. This will allow the REPL to load the required symbols at runtime. SwiftPM does this by synthesizing a special product that contains all of the library targets of the root package. This special product is only built when using the --repl option and doesn’t affect other package manager operations.

Checkout the pull request that implemented this functionality for full implementation details!

Conclusion

REPL support for Swift packages will further enhance the REPL environment and enable easier experimentation for library package authors and consumers. The feature is available to try in the latest trunk snapshot. If you find bugs or have enhancement requests, please file a JIRA!

Questions?

If you have questions and are interested in learning more, check out the related discussion thread in the Swift forums.