1. Hello World

Jun 16, 2020 | 16:57
Class vs struct vs enum, function overloading vs default parameter values, String concatenation vs interpolation, static vs global function.

Listen and/or subscribe via:

Hello everyone, today we're looking at the Hello World exercise found at exercism.io. More specifically we're looking at a solution I've written from the perspective of a new-to-Swift developer who is not super familiar with Swift's language features. Let's dive in.

Exercise

This particular exercise doesn't need much explanation. Basically we're just going to make the tests pass.

class HelloWorldTests: XCTestCase {
    func testNoName() {
        let expected = "Hello, World!"
        XCTAssertEqual(
            HelloWorld.hello(), expected, 
            "When given no name, we should greet the world!"
        )
    }

    func testSampleName() {
        let expected = "Hello, Alice!"
        XCTAssertEqual(
            HelloWorld.hello("Alice"), expected, 
            "When given 'Alice' we should greet Alice!"
        )
    }
    ...
}

So we need a type HelloWorld with a static function hello() that can be called with or without a String argument.

New-to-Swift implementation

Here is a solution from the perspective of a new-to-Swift developer not super familiar with Swift's language features.

class HelloWorld {
    static func hello() -> String {
        return "Hello, World!"
    }

    static func hello(_ name: String) -> String {
        return "Hello, " + name + "!"
    }
}

Class vs struct vs enum

The HelloWorld type here is being defined as a class. This compiles, passes the tests, and is immediately familiar to new-to-Swift developers. However in Swift we have two other options which are often more appropriate depending on the problem being solved. I recommend defaulting to struct, then only moving to class if the instances of this type need reference-semantics or inheritance. However there are no instances in the test suite. This type is only serving as a namespace for the hello() function. In that case I'd use an enum with no cases or initializers. That way we have a type which is unable to produce instances.

enum HelloWorld {
    static func hello() -> String {
        return "Hello, World!"
    }

    static func hello(_ name: String) -> String {
        return "Hello, " + name + "!"
    }
}

What do you think? class, struct, or enum?

Function overloading vs default parameter values

Function overloading works and is familiar to new-to-Swift developers but it results in two almost identical implementations. I recommend giving the second implementation a default parameter value of "World", then removing the first implementation entirely.

enum HelloWorld {
    static func hello(_ name: String = "World") -> String {
        return "Hello, " + name + "!"
    }
}

What do you think? Function overloading or default parameter values?

String concatenation vs interpolation

String concatenation works and is familiar but doesn't feel very Swift-y. I recommend using String interpolation instead. It has less syntax and will be appreciated by other Swift developers reading the code.

enum HelloWorld {
    static func hello(_ name: String = "World") -> String {
        return "Hello, \(name)!"
    }
}

What do you think? String concatenation or interpolation?

Static vs global function

It turns out we don't need the HelloWorld type at all. This exercise can be solved with a global function because the module name happens to be HelloWorld. When the compiler sees the code in the test suite HelloWorld.hello() it first looks for a type called HelloWorld, then if it can't find one looks for a module called HelloWorld with a global function hello().

func hello(_ name: String = "World") -> String {
    return "Hello, \(name)!"
}

What do you think? Static or global function?

Next episode

Next episode we'll be looking at the Leap exercise found at exercism.io. Feel free to complete that exercise before the next episode and we can compare our answers.

Take care everyone.