Flinging poo in Swift

Thanks to one of the genuine upsides of Swift, full Unicode support in the compiler itself, you can do:

enum EmojiErrors: Error {
    case 💩
}

let 💩 = EmojiErrors.💩

Finally you can express yourself honestly within your program:

throw(💩)

Or, if you prefer to be polite & British about it:

func flinging<E: Error>(_ error: E) throws -> Never {
    throw(error)
}

try flinging(💩)

You can go further, if you like, and have all manner of interesting things thrown about your program:

enum EmojiErrors: Error {
    case 💩
    case 🖕
    case 🐞
    case 🎮
    case 🏈
    case 💣
    case 📱
    case 💻
    case 🖥
    case 🎉
}

ambiguous reference to member ‘joined()’

You can readily tell that Swift was created by a C++ fanatic, by its fucking obtuse error messages.

⤹ Me             Swift compiler ⤵︎
"What do you want" scene from The Notebook

In today’s episode of “what the fuck do you want, compiler?”, we tackle:

foo.swift:186:39: error: ambiguous reference to member 'joined()'
       log.debug("\(thingies.joined(separator: ", "))")
                    ^~~~~~~~
Swift.BidirectionalCollection:27:17: note: found this candidate
 public func joined() -> FlattenBidirectionalCollection<Self>
             ^
Swift.Sequence:27:17: note: found this candidate
 public func joined() -> FlattenSequence<Self>
             ^
Swift.Sequence:18:17: note: found this candidate
 public func joined<Separator : Sequence where Separator.Iterator.Element == Iterator.Element.Iterator.Element>(separator: Separator) -> JoinedSequence<Self>
             ^
Swift.Sequence:16:17: note: found this candidate
 public func joined(separator: String = default) -> String
             ^
Swift.Collection:27:17: note: found this candidate
 public func joined() -> FlattenCollection<Self>
             ^

For context, ‘thingies’ is an array of a custom type.

What the compiler wishes it could say, if it weren’t incompetent, is that every one of Array’s ‘joined’ implementations are conditional.  The one that I want is is the second last one, but it is only defined for Array<String> specifically.  No other Array types.

Similarly every other one is conditional on the Element type within the Array being a specific type or protocol, none of which happen to apply to the types I’m using in my Array.

Now, my intuition is that since my type is CustomDebugStringConvertible, that Swift would know then how to convert my type to a String and then go from there.  For better or worse, however, it does not.  Instead you have to do it manually, e.g.:

log.debug("\(thingies.map({ String(describing: $0) }).joined(separator: ", "))")

And you can probably tell from that alone that I’m very used to Objective-C, where it’s very easy to write what you intend and get the results you intend.

Swift’s String.write(toFile:…) can’t handle tildes

let path = "~/Desktop/sigh.txt"
try "WTF?".write(toFile: path, atomically: true, encoding: .utf8)

Result?  Explode:

Error Domain=NSCocoaErrorDomain Code=4 "The folder “sigh.txt” doesn’t exist." UserInfo={NSFilePath=~/Desktop/sigh.txt, NSUserStringVariant=Folder, NSUnderlyingError=0x1018110b0 {Error Domain=NSPOSIXErrorDomain Code=2 "No such file or directory"}}

And since there’s no documentation on that write() method, and this is obviously a perfectly reasonable request that can’t sanely yield that bizarre error message, you could be forgiven for having no idea how to fix this.

Long story short, in macOS Sierra at least that write() method happens to be implemented as NSString’s writeToFile:atomically:encoding:error: method.  If you look at the documentation for that method, it states:

path:  The file to which to write the receiver.  If path contains a tilde (~) character, you must expand it with stringByExpandingTildeInPath before invoking this method.

WTF it can’t actually say that in the Swift method’s documentation (or indeed why it has none at all), or why the exception thrown can’t at least give the real reason, or of course WTF it can’t just do the right thing and handle tildes in the path…  I have no idea.  This smells like a combination of laziness, snafu, and poor judgement in carrying over bad behaviours and design flaws from Objective-C.

So instead you have to do:

try "WTF?".write(toFile: (path as NSString).expandingTildeInPath, atomically: true, encoding: .utf8)

Sigh.