inessential by Brent Simmons

Swift Bikeshed Week Three: LangValue

(Note: this is a data modeling and error-handling exercise. No tricky algorithms are needed.)

Pretend you’re writing the runtime for a language.

It’s a simple language with just three data types: integer, string, and table.

Your mean, meddling, micro-managing boss told you to use a single thing to represent values in this language, and that thing has to be named LangValue.

Your mean boss — who hates classes and Objective-C — also said she wants to see two solutions. Here are your choices:

Pick any two.

Spec

The API for creation must be something like this, though not necessarily exactly like this (could be one or more init methods instead, for instance):

LangValueWithInteger(n: Int) -> LangValue
LangValueWithString(s: String) -> LangValue
LangValueTable() -> LangValue //empty table

LangValue API must be conceptually like this:

type (readonly) -> LangValueType.Integer, .String, or .Table

The following throw LangCoercionError for tables:

integerValue (readonly) throws -> Int
stringValue (readonly) throws -> String
valueByAddingValue(LangValue: value) throws -> LangValue

A String can be coerced to an Integer via whatever built-in coercion Swift (or NSString) provides. Don’t sweat this. It’s okay if it returns 0 in cases like “some string”.

When a String and an Integer are being added, the Integer is coerced to a String, and the result is LangValueType.String. 123 + "123" == "123123"

(In other words: though Strings can be coerced to Integers, Integers are coerced to Strings when doing automatic coercion.)

The following throw LangCoercionError when the receiver isn’t a table.

setObjectForKey(object: LangValue, key: String) throws
removeObjectForKey(key: String) throws
objectForKey(key: String) throws -> LangValue?
keys() throws -> [String]

Tests

Adding values

This code (or the equivalent) must work:

let s = LangValueWithString("This is a string")
let n = LangValueWithInteger(42)
let someValues = [s, n]

func addIntegerValuesInArray(values: [LangValue]) -> LangValue {

	// Return LangValueType.Integer object
	...write code here
}

func addStringValuesInArray(values: [LangValue]) -> LangValue {

	// Return LangValueType.String object
	...write code here
	}

let intResult = addIntegerValuesInArray(someValues)
assert(intResult.integerValue() == 42)

let stringResult = addStringValuesInArray(someValues)
assert(stringResult.stringValue() == "This is a string42"

let unknownResult = s.valueByAddingValue(n)
assert(unknownResult.type == .String)

Tables and Sets

This code (or the equivalent) must also work:

let t = LangValueTable()
t.setObjectForKey(LangValueWithInteger(10), "SomeInt")
let someString = LangValueWithString("Some string")
t.setObjectForKey(someString, "SomeString")

let subtable = LangValueTable()
subtable.setObjectForKey(LangValueWithInteger(50), "SubtableInt")
let anotherString = LangValueWithString("Another string")
subtable.setObjectForKey(anotherString, "SubtableString")
t.setObjectForKey(subtable, "Subtable")

func recursiveSetOfStringsInTable(table: LangValue) -> Set<LangValue> {
	// Get all LangValueType.String objects from a table and its subtables and return them as a Swift set.
	...write code here
}

let setOfStrings = recursiveSetOfStringsInTable(t)
assert(setOfStrings == Set([someString, anotherString])

Note: The code in both tests probably needs error handling.