Dear Erica: How do I simplify `Swift.print`?

Dear Erica:

In other languages (Python mostly comes to mind, but I think ml and probably Haskell as well) you can essentially create a new name for a function by assigning it to a variable and use that instead.  All type information and usage follows with it.

I use it a lot in Python to have a local variable that’s  already bound to some otherwise nested module call. It saves some lookup time and textual typing. (Gotta be explicit since syntactic typing is relevant in Swift.) In trying to work around the problem of “print” calling  NSDocument.print, I tried declaring a val at the top level:

let Print = Swift.print // for print to console

The compiler seemed happy with the definition but when I tried to use it as in:

Print(“in makeWindowControllers”)

I got:

Hitting Fix did nothing but I wasn’t really expecting much (this is Xcode 9.0 beta). I assumed it wasn’t  telling me to literally put <#String#> in as a second parameter but that the type inference was inferring a type of String for  parameter #2  It didn’t really make any sense (why would a second parameter be required for a variadic argument?) If two, why not 3…?

So I tried the following but to no avail:

Print(“in makeWindowControllers","two”)

One can obviously write a full function which works fine:

func Print(_ items:Any...) -> () {
    print(items) 
} 

Print("in makeWindowControllers")

It’s pretty simple but seems unnecessarily heavyweight. I can’t seem to find any way to do a variadic closure. Is this possibly a bug or am I missing something obvious? Is there any reason the simple value declaration shouldn’t work? I know I’ve seen complaints about the lack of an Apply function in Swift. Not sure if this is related. My functional mojo is somewhat lacking…

This is a terrific question, and I’m going to answer it in several parts.

First, the full signature of Swift’s version of print is

public func print<Target>(_ items: Any...,
    separator: String = default, 
    terminator: String = default, 
    to output: inout Target) 
    where Target : TextOutputStream

See all those default items?  They don’t travel nicely to closures.  Second, take careful note of the variadic items, because you can’t pass them along by redirecting them to another function. Your “full function” actually prints an array of the items you pass:

Print("Hello world") // prints ["Hello World"]

The only way around this for 10.12 and earlier is to re-implement print (or a reasonable facsimile). Having tried that this morning, I warn you, it is a doozy. Seriously. You wouldn’t believe how many functions and files go into this one little call.

Fortunately, this name-overlap problem (NSView.print vs Swift.print, etc) is fixed in High Sierra. The 10.13 release notes write:

print() methods in Swift: NSWindow, NSView, NSDocument’s print() instance methods have been renamed to printWindow(), printView(), and printDocument() respectively in Swift 4. This fixes the unexpected experience where adding debug logging to a subclass of one of these instances shows a print panel instead.

You can help the variadics don’t propagate cause (“El viva variadics!“) by filing a radar. Go ahead and dupe, mentioning rdar://problem/12134482.

For the moment, here’s a less capable workaround you can use. It does pretty much what you want with slightly reduced capabilities (and complexity) compared to the built-in Swift.print solution.

public func sprint(
    _ items: Any...,
    separator: String = " ",
    terminator: String = "\n")
{
    var prefix = ""
    for item in items {
        Swift.print(prefix, terminator: "")
        Swift.print(item, terminator: "")
        prefix = separator
    }
    Swift.print(terminator)
}

It’s kind of a production-unfriendly workaround for just avoiding Swift.print, but I hope this helps anyway.

Thanks Dave, Tim, Stephen, Caleb

Comments are closed.