Your Keep in Swift
The program you already wrote, in the language closest to the one you know.
This is the first of three guides for reading your own keep in a bigger language, and it is the short one. That is the finding, not an apology: of the three languages lux translates into, Swift is the one where the least changes. Most of what follows is naming punctuation.
You should have read When lux Feels Small first, or at least be at the point it describes — you have built something in lux, you have hit a wall, and you want to know what is on the other side of it.
Get it in front of you
Nothing below means much without the file open next to it. Converting costs nothing and needs nothing installed:
$ lux convert swift world.lux > Keep.swift
Open it. The first thing you will notice is that you do not recognize the top of the file, and the second is that you should not — roughly the first third is a prelude lux wrote, not you. It teaches Swift to print a struct the way lux prints it, to render a float the way lux renders it, and to read and write files with the same error wording. None of it is yours and none of it is worth reading yet.
Scroll until you hit this, and start there:
struct World: Equatable {
var room: Room
var items: [String]
var doorOpen: Bool
var playing: Bool
}
From that line to the end of the file is your program, function for function, in the order you wrote them. That is the thing to notice before any of the details: nothing was rearranged, nothing was cleverly restructured. Your keep is still your keep.
To run it you need Swift itself. That is a real install, and it has nothing to do with programming. On a Mac it comes with Xcode. On Linux there are packages. Ask for help if it fights you, and do not let a bad half hour with an installer convince you the language is hard.
$ swiftc -o keep Keep.swift
$ ./keep
It plays. Same rooms, same locked door, same secret in the chamber — the output is identical to lux run, line for line, because that is checked on every release.
What is already the same
Here is the whole translation, as a table. Read down the left column and you will find nothing you have not written a hundred times.
| In your lux | In the Swift |
|---|---|
struct World { … } | struct World: Equatable { … } |
enum Room { entrance … } | enum Room { case entrance … } |
func describe(room: Room) -> string | func describe(_ room: Room) -> String |
match room { … } | switch room { … } |
entrance => "…" | case .entrance: return "…" |
_ => … | default: … |
Option<Room> | Room? |
none | nil |
[string], bool, int | [String], Bool, Int |
print(…) | print(…) |
| the file runs top to bottom | the file runs top to bottom |
That last row matters more than it looks. Rust and Go both make you write a main before anything can happen. Swift, like lux, lets the file be the program — so the loop at the bottom of your keep is still just sitting at the bottom of the file, running.
Here is describe, the purest case of all. Your version, then the Swift, unedited:
lux
func describe(room: Room) -> string {
return match room {
entrance => "You stand at the mouth of an old stone keep…"
hall => "A wide hall, its banners long rotted…"
}
}
Swift
func describe(_ room: Room) -> String {
switch room {
case .entrance:
return "You stand at the mouth of an old stone keep…"
case .hall:
return "A wide hall, its banners long rotted…"
}
}
Four differences, all of them spelling: an underscore, a capital String, switch for match, and case .entrance: for entrance =>. The idea underneath — a closed set of rooms, one arm each, the compiler making you cover them all — did not move at all.
The five new marks
Five things in that file are not lux at all. None of them is hard, and knowing what they are called is most of the work.
The underscore in front of a parameter. func has(_ items: [String], …). In Swift, a caller normally has to name each argument — has(items: pack, thing: "key"). The underscore turns that off, so the translated calls look exactly like your lux calls. Written by hand, Swift code often keeps the labels, and they read nicely: move(from: hall, to: cellar). lux has no version of this at all.
The leading dot. case .entrance:, not case Room.entrance:. Swift already knows you are switching on a Room, so it lets you drop the type name. You will see this everywhere and it takes about a day to stop noticing.
: Equatable after a type name. This is the one genuinely new idea, so slow down here. It says this type can be compared with ==. In lux, comparing two rooms just worked; in Swift, being comparable is a capability a type either has or does not, and you say so by name. That thing after the colon is called a protocol, and protocols are one of the biggest ideas in Swift — a list of what a type can do, kept separate from what it is.
var inside a struct. Your lux struct just lists field names. Swift's fields each say whether they can change, the same let and var choice you already make for names. lux made that decision for you.
The question mark. Room? is Swift's spelling of Option<Room>, and it is the one that deserves its own section.
Where Swift is better
lux taught you that a value which might be missing has to say so in its type, and that the only way to get at it is to open it up with match and handle both cases. Swift agrees with every word of that. It just gives you far better tools for doing it.
First, the type is a punctuation mark. Option<Room> becomes Room? — a room, or nothing. And none is spelled nil.
Second, and this is the part you will actually feel: you do not have to write a switch every time. Here is walk as lux translated it — faithful, and a bit heavy:
func walk(_ w: World, _ dir: String) -> World {
switch exit(w.room, dir) {
case .some(let r):
return tryEnter(w, r)
case .none:
return cantGo(w)
}
}
And here is what a Swift programmer writes:
func walk(_ w: World, _ dir: String) -> World {
guard let r = exit(w.room, dir) else { return cantGo(w) }
return tryEnter(w, r)
}
guard let reads as get me the room, or get out. The missing case is handled on one line and then forgotten, and the rest of the function gets to work with a real Room rather than a maybe-room. There is a matching if let for when you want to do something in both branches.
Notice what did not change: you still cannot forget. Swift will not let you use a Room? as a Room without opening it. It only lets you open it in about four different ways depending on which reads best, where lux gave you one.
What you would actually write
The translation is faithful, not idiomatic. It is a careful sentence-by-sentence rendering by somebody determined not to change your meaning. That is exactly what you want from a translator, and not at all how a native speaker talks.
Your has helper is the clearest example. Here is what lux emitted:
func has(_ items: [String], _ thing: String) -> Bool {
for it in items {
if it.unicodeScalars.elementsEqual(thing.unicodeScalars) {
return true
}
}
return false
}
And here is the whole thing, the way you would write it in Swift:
func has(_ items: [String], _ thing: String) -> Bool {
items.contains(thing)
}
Two things happened there. Swift's arrays already know how to answer "is this in you?", so the loop you had to write by hand in lux is a method that comes with the type. And a function whose whole body is one expression does not need the word return.
That odd unicodeScalars line is lux being careful rather than Swift being complicated, and it is explained in the graduation guide. Writing == in your own Swift is fine.
The habit to take from this: read the translation to find your program, then look up how the thing you wrote is normally done. The translation is a map of where everything went, not a style guide.
The walls, gone
The walls that told you it was time to leave are not walls here. This is the payoff, and the code is smaller than you would expect.
You wanted text that behaves. The reason typing paris got you marked wrong. lux can split a string and search one, and that is the whole of its string handling; Swift's goes on for pages:
answer == "Paris".lowercased() // true
Lowercasing, trimming, padding, searching, and formatting are all one call away, and none of them is a thing you have to write first. That is the real change — not any single function, but never again having to build the small tool before you can start on the actual problem.
You wanted to look a room up instead of matching down a list. Swift has dictionaries, and they are ordinary:
var exits: [String: Room] = ["north": .hall, "east": .cellar]
let next = exits["north"] // a Room? — missing is still a shape
Note the type of that lookup. Swift did not abandon the lesson lux taught you; asking for a key that is not there hands back nothing, and you still have to open it.
You wanted more than one file. Swift lets you split a program across as many as you like, and any file can use what any other file defines. You will also start using code other people wrote, and that is the thing that will make you fastest.
And you can now pass a function to a function. That .map(String.init) up there is doing something lux has no version of — handing one function to another as a value. It is what makes map, filter, and sorted possible, and it is the single biggest shift in how code looks.
Worth knowing: everything in this section is why the guide is short. Swift did not change how you think about your keep — it removed the reasons you had to stop building it.
Where to go from here
Do the thing you were blocked on. That is the recommendation.
You left lux because you wanted something specific — real commands, a bigger map, a wing joined to a friend's. Build that first, in Swift, on the keep you already have. You will look things up as you go and you will remember them, because you wanted them before you looked them up. That is a much better way in than working through a tutorial about a program you do not care about.
Three things to reach for early, in this order. Methods on your own types — Swift lets you attach a function to a struct or enum so it travels with it — that is how items.contains(…) works, and how your own code will start to read. Protocols, the idea behind that : Equatable, where Swift stops looking like lux and starts looking like itself. And the standard library, large and good and full of things you have been writing by hand.
What you will not have to relearn: types, functions as boundaries, enums with a closed set of cases, matching that makes you cover every one, missing as a shape rather than a hole, and building a new value instead of poking an old one. You brought all of that with you. It was never really lux's.
In your terminal: lux convert swift on anything you want to see translated — it stays useful for a while after you have moved. On the web: When lux Feels Small is the guide to the move itself, Go and Rust are the same trip into the other two — worth a skim even if you have chosen, since what each one does with the same program of yours is its own lesson — and What lux Leaves Out explains why lux was shaped the way it was.