Swift and Visual Studio Code

If you've been living under a rock, you might not know that Microsoft released a new editor (focused on web development): Visual Studio Code.

So… is it really just for web development? Honestly, it doesn't matter if it starts out that way because the thing that Microsoft tends to always get right is extensibility. We'll be able to start building plug-ins as the product matures to enable all sorts of development, including Swift!

Syntax Highlighting

Ok, the first thing that we need are the nice colors! This is actually fairly trivial to do:

  1. Open up the package contents of the Visual Studio Code app 2. Navigate to Contents/Resources/app/plugins 3. Create a new folder: vs.languages.swift 4. Place these three files into the created folder:
    1. swiftDef.js
    2. swiftMain.js
    3. ticino.plugin.json 5. Restart Visual Studio Code and open a Swift file!

You should see something like this:

Swift with basic syntax highlighting.

Note that this is just the most basic of highlighting that supports basic comments, strings, and keywords. More to come later!

Building

Next up, build errors! Again, this is also quite trivial to setup. You'll need to create a new build task. You can do this in a couple of ways. The easiest is:

  1. ‚åòP to bring up the command palette 2. Type build and press RETURN 3. There will be a prompt that you have not tasks; create the task 4. This will create a .settings folder with a tasks.json file

The contents of the file should be:

{
    "version": "0.1.0",
    "command": "swiftc",
    "showOutput": "silent",

    "args": ["main.swift"],

    "problemMatcher": {
        "pattern": {
            "regexp": "^(.*):(\\d+):(\\d+):\\s+(warning|error):\\s+(.*)$",
            "file": 1,
            "line": 2,
            "column": 3,
            "severity": 4,
            "message": 5
        }
    }
}

Now, you can press ‚áß‚åòB now to build. If you have an error, press ‚áß‚åòM to show the error pane.

Swift with a build error shown.

Next Steps

I'll probably be taking a look at making the editing experience better by playing around with the options for syntax highlighting. I also want to take a look at building a language service, though, officially this is not yet supported.

Goodie Bin

Ok… if you want to contribute to this, I've actually posted a fill GitHub repo here: https://github.com/owensd/vscode-swift.

What I actually have done, instead of creating a directory, is create a link within the Visual Studio Code package to my repo location on disk.

  1. cd /Applications/Visual\ Studio\ Code.app/Contents/Resources/app/plugins 2. ln -s <path/to/repo> vs.languages.swift 3. Restart Visual Studio Code

Voilà!

Swift and Visual Studio Code

Switch Statement Matching

What is the output of this problem?

let x = 5, y = 10

switch (x, y) {
case (_, _):
    println("_, _")

case (5, _):
    println("5, _")

case (_, 10):
    println("_, 10")

case (5, 10):
    println("5, 10")
}

If you said, "", then great! If you're sad that case (5, 10): doesn't match… well, you're not alone.

Friendly reminder, always read your swift-statements like a chain of if-statements. With pattern matching in Swift, this could be the source of some inadvertent bugs, especially when adding new case clauses.

UPDATE: I filed rdar://21001503 about having a warning for case statements that can not be reached.

Switch Statement Matching

Brent’s Week 3 Bike Sheed

If you're not following Brent Simmons, then go here first: http://inessential.com/langvalue.

Ok, now that you've read that, you should probably stop reading here until you go and try to solve the problems there. For extra points, only pick one of the struct/enum choices and pick one of the protocol based choices.

I'll wait…


If you've been following along with any of Brent's posts, you might notice that he has (intentionally?) created a problem that will help you uncover some of the limitations of Swift's type system based on some of his own frustrations (see his KVC diary posts). Nonetheless, it is good to compare what options are on the table when a problem like he presented is put before us.

Now, if you've only played with Swift on the surface, you might not know the gotcha's yet. Also, you've probably heard a bit about "PROTOCOLS, PROTOCOLS, PROTOCOLS!". Naturally, you might say to yourself, "AHA! The protocol solution must be the 'right one', 'best one', or the 'easiest one' to implement."

However, if you have been around Swift for a while, I'm sure you noticed that Brent has fiendishly put a requirement of Set<T>.

Meme - Train on bridge falling off, Caption: Use Protocols They Said... It's Awesome They Said...

So there's a dirty little (apparent) problem with protocols today… once they get a Self requirement, hetergenous usage of them goes out the window for any strongly typed collection.

But… maybe this is a sign. Maybe the "protocols first" approach tells us something. After all, implementing the protocol version of Brent's question is actually not even supported today in Swift (option 4 at least).

Solving the Problem

Let me suggest something: the spec artificially makes the problem harder and implies that protocols should be used in a way that I do not think Swift intends you to use them. Protocols are not types, they do not behave like types, and they cannot be used like types.

I think the better option to solving the problem presented is option #5: using enums and protocols.

Enums

The problem statement is very clear: there are three, and only three, different type constructs that are allowed. Namely:

  • Integer
  • String
  • Table

To me, this clearly implies that an enum is going to be the right structure to use.

enum LangValue {
    case IntegerValue(Int)
    case StringValue(String)
    indirect case TableValue([String:LangValue])
}

Beautiful. Seriously, I think that is an extremely clear and precise model of exactly what we want.

Now, we need to add a type function. So the complete, initial model looks like this:

enum LangValueType {
    case Integer
    case String
    case Table
}

enum LangValue {
    case IntegerValue(Int)
    case StringValue(String)
    indirect case TableValue([String:LangValue])

    var type: LangValueType {
        switch self {
        case .IntegerValue(_): return .Integer
        case .StringValue(_): return .String
        case .TableValue(_): return .Table
        }
    }
}

Protocols

OK, now here is where I think protocols should be used. You see, the spec now asks for four different classes of behaviors:

  • Convertible to an Integer
  • Convertible to a String
  • Addable
  • Storable (my term for the dictionary-like operations)

To me, this screams protocols.

protocol IntegerConvertible {
    func integerValue() throws -> Int
}

protocol StringConvertible {
    func stringValue() throws -> String
}

protocol Addable {
    typealias AddableType
    func add(other: AddableType) throws -> AddableType
}

protocol Storeable {
    typealias ValueType
    typealias KeyType

    mutating func set(object object: ValueType, forKey key: KeyType) throws
    mutating func remove(forKey key: KeyType) throws
    func object(forKey key: KeyType) throws -> ValueType?
    func keys() throws -> [KeyType]
}

Notice something: none of these protocols are tied to the LangValue type. If a type is needed, a typealias is used to make this generic so that these protocols can be applied to any type. This is key (and goes a bit against some of my previous recommendations about not making protocols generic).

Now it's simply a matter of applying these protocols to our type:

extension LangValue : IntegerConvertible {
    func integerValue() throws -> Int {
        switch self {
        case let IntegerValue(value): return value
        case let StringValue(value): return (value as NSString).integerValue
        default: throw LangCoercionError.InvalidInteger
        }
    }
}

extension LangValue : StringConvertible {
    func stringValue() throws -> String {
        switch self {
        case let IntegerValue(value): return NSString(format: "%d", value) as String
        case let StringValue(value): return value
        default: throw LangCoercionError.InvalidString
        }
    }
}

extension LangValue : Addable {
    func add(other: LangValue) throws -> LangValue {
        switch (self, other) {
        case let (.IntegerValue(lvalue), .IntegerValue(rvalue)):
            return .IntegerValue(lvalue + rvalue)

        case (.StringValue(_), .IntegerValue(_)): fallthrough
        case (.IntegerValue(_), .StringValue(_)): fallthrough
        case (.StringValue(_), .StringValue(_)):
            return try LangValue.StringValue(self.stringValue() + other.stringValue())

        default: throw LangCoercionError.InvalidAddition
        }
    }
}

extension LangValue : Storeable {
    mutating func set(object object: LangValue, forKey key: String) throws {
        switch self {
        case let .TableValue(table):
            var copy = table
            copy[key] = object
            self = LangValue.TableValue(copy)

        default: LangCoercionError.NotOfTypeTable
        }
    }

    mutating func remove(forKey key: String) throws {
        switch self {
        case let .TableValue(table):
            var copy = table
            copy.removeValueForKey(key)
            self = LangValue.TableValue(copy)

        default: throw LangCoercionError.NotOfTypeTable
        }
    }

    func object(forKey key: String) throws -> LangValue? {
        switch self {
        case var .TableValue(table):
            return table[key]

        default: throw LangCoercionError.NotOfTypeTable
        }
    }

    func keys() throws -> [String] {
        switch self {
        case let .TableValue(table):
            return Array(table.keys)

        default: throw LangCoercionError.NotOfTypeTable
        }
    }
}

Conclusion

So that's it, that's the basic approach to the problem that I think is a concise, clear model of the specification. It doesn't exactly fit the asks from the four options, but I think it is the better approach.

Full source code here: https://gist.github.com/owensd/33d85872c15c2b496515

Brent’s Week 3 Bike Sheed