Figure

A blog about Swift and iOS development.

Stupid Disambiguation Tricks

Swift does a lot of work with inference. This prevents a lot of boilerplate and redundancy:

//Long and boring...
let url:NSURL = NSURL(string:"http://foo.com") 
self.localMethod(self.myProperty, MyEnum.SomeType)

//Better coding through inference!
let url = NSURL(string:"http://foo.com")
localMethod(myProperty, .SomeType)

But sometimes our code is ambiguous leading Swift to infer the wrong things. For example, let's say we want an optional string variable with an initial value of nil:

//Ambiguous. What type should this be?
let optionalString = nil

Swift can't infer a type from nilany optional type could conceivably have a value of nil. So we need to disambiguate by being more explicit or providing more context.

//Provides explicit value:
let optionalString = Optional<String>()

//Provides context for the assignment:
let optionalString:String? = nil

Another common case involves having a property and parameter with the same name. Normally we don't have to explicitly refer to properties through self. But in this case, if we don't, Swift will assume we want to assign to the parameter instead of the property… which is illegal:

var tautology:String
init(tautology:String){
  //Bad. The parameter is read-only.
  tautology = tautology

  //Good. Explicit use of "self".
  self.tautology = tautology
}

But there are other, weirder situations where disambiguation is needed:

struct MyThing{
  func map(transform:SomeClosure){
    //Wrong number of arguments?!
    map(self, transform);
  }
}

This looks legit. We have a method called map that's using Swift's top-level map function in its implementation. Why the sad compiler?

It turns out when Swift sees map in the body of our method, it assumes we're talking about self.map and tries to call itself recursively (with the wrong parameters, hence the error).

To work around this, we need to let the compiler know we want to use standard library's map by prepending the call with Swift.:

struct MyThing{
  func map(transform:SomeClosure){
    Swift.map(self, transform);
  }
}

Which works great for the standard library functions. But what if we want to call our own top-level routine:

func foo(){ ... }
struct MyThing{
  func foo(){
    //Bad. Calls self.foo recursively
    foo() 
  }
}

No problem. There's nothing magic about that Swift. specifier. It's just the name of the module the standard library happens to be defined in. foo is defined in our own module, so we prepend its name to the call instead:1

//In module "MyProject":
func foo(){ ... }
struct MyThing{
  func foo(){
    MyProject.foo()
  }
}

Which works great. Until…

What happens when you define a type with the same name as your module that contains a method with the same name as a top-level function that you want to call from within its implementation?2

//In module "MyProject":
func foo(){ ... }
struct MyProject{
  func foo(){
    //Uh-oh! Recursive again.
    MyProject.foo()
  }
}

Kablamoo! Because MyProject is also the name of our struct, Swift is trying to call the foo method on it instead of our module resulting in another recursive loop.3

So how do we tell Swift we're trying to specify a module name, not a type?

Um… I don't think we can. Or, at least, I haven't been able to find a way, yet. If you know of something, tell me about it!

In the meantime, I work around this problem using private functions that wrap their ambiguously-named counterparts:

//In module "MyProject":
func foo(){ ... }
private func fooAlias(){ foo() }

struct MyProject{
  func foo(){
    fooAlias()
  }
}

What can I say? Not everything in Swift is all rainbows and monads. Its implicit-and-morally-opposed-to-external-tweaking namespaces and pathologically-convinced-we-want-the-most-local-thing-always inference engine are perfect examples of tools that can sometimes get us backed into an ugly corner.

But those cases are: rare; usually addressable with additional context; and, for all the concision, consistency, and convenience we get in exchange, well worth the tradeoff.


1: Normally our module name is the same as our project name. But if we want to look it up, we can check our Build Settings for "Project Module Name" under the "Packaging" section.↩︎

2: The asymmetry of programming: it only takes 68 bytes to implement what it takes 190 bytes to explain.↩︎

3: The actual error is "Missing argument for parameter #1 in call" because methods are, in fact, curried functions in Swift.↩︎


Hit me up on twitter (@jemmons) to continue the conversation.