Four Key Reasons to Learn Markdown
Back-End Leveling UpWriting documentation is fun—really, really fun. I know some engineers may disagree with me, but as a technical writer, creating quality documentation that will...
At Big Nerd Ranch, we love certain things. We love coffee, we love fitness and we
really love technology. When Apple announced Swift, I found it really hard to
resist cracking open the
free
books
and seeing what it’s all about.
I write Ruby every day and love the language, and
there’s a lot for a Rubyist to love here. Apple’s cooking up something special.
With Swift, Apple introduced playgrounds, a way of getting inline feedback from code as it’s typed into Xcode.
This is really helpful in discovering the language, because you can jump to the
documentation in Xcode and really get down to business.
Apple included a Swift
REPL in
their Xcode 6 betas. Additionally, scripts provided as arguments will be run
with “immediate execution.”
The usage is pretty simple. If you run the command, you get the Swift REPL:
% xcrun swift
Welcome to Swift! Type :help for assistance.
1>
If you reference a Swift file, it will compile and run it:
% xcrun swift harvey.swift
I am the batman!
I like to keep an alias in my environment:
alias swift="xcrun swift"
Apple seems to be investing in Swift as a scripting language as well. You can
even “shebang” Swift scripts. Check out Apple’s “Files and Initialization” blog post.
Swift makes the distinction between constant and variable values during
assignment. In Ruby, we indicate constant values by naming their identifier
with a capital first letter. By convention, we use ALL CAPS for constants and
CamelCase for types. Any other identifier is a variable value.
WOW_GREAT_DEV = 'Jay'
Dev = Struct.new(:name)
dev = Dev.new(WOW_GREAT_DEV)
puts dev.name
# Jay
WOW_GREAT_DEV = 'Dog' # this produces a warning
dev = Dev.new("Dog")
puts dev.name
# Dog
Constant and variable values in Swift are created using the let
and var
keywords respectively.
let wowGreatDev = "Jay"
struct Dev {
var name: String
}
var dev = Dev(name: wowGreatDev)
println(dev.name)
# Jay
wowGreatDev = "Dog" // this produces an error
dev = Dev(name: "Dog")
println(dev.name)
Tuples are a great, low-ceremony way of passing around a collection of related
values. They allow us to collect things into a compound value. Unlike other
structures in Swift, they don’t have much behavior.
In Ruby, we will sometimes use a Hash
to collect related values. Consider an
options hash in Ruby:
def process(options = {})
puts options.fetch(:force, false)
puts options.fetch(:format, :json)
end
process
process(force: true, format: :html)
# false
# :json
# true
# :html
We might reach for a tuple in Swift to do something similar. A notable
difference is that we must explicitly state the valid options:
func process(options: (force: Bool, format: String) = (force: false, format: "json")) {
println(options.force)
println(options.format)
}
process()
process(options: (force: true, format: "html"))
// false
// "json"
// true
// "html"
Notice one difference is that the Swift implementation uses an “external
parameter” to set the options
parameter to the method.
In both Ruby and Swift, certain values may be destructured during assignment to
multiple variables. As long as the grouping on the left matches the structure
on the right, the collection is destructured to match. This may be done with
arrays in Ruby:
x,(y,z) = [1,[2,3]]
puts x
puts y
puts z
# 1
# 2
# 3
Similarly, Swift tuples can be destructured during assignment:
let (x,(y,z)) = (1,(2,3))
println(x)
println(y)
println(z)
// 1
// 2
// 3
Another example of Ruby destructuring is as parameters to a message call.
foo = lambda do |(x,(y,z))|
puts x
puts y
puts z
end
bar = [1,[2,3]]
foo.call(bar)
# 1
# 2
# 3
Unlike Ruby, Swift argument lists are not treated the same as assignment. To
destructure an argument in Swift, it must be done using assignment in the body
of the method or closure:
let foo = { bar: (Int, (Int, Int)) in
let (x,(y,z)) = bar
println(x)
println(y)
println(z)
}
bar = (1,(2,3))
foo(bar)
// 1
// 2
// 3
Another interesting feature of Swift is value bindings. In combination with a
switch
statement, you can execute separate blocks of code based on the
destructured state of a tuple. You can even add conditions to the statements to
direct matches further. The canonical example is directing execution by
anchoring parts of a point
tuple:
func printPoint(point: (Int, Int)) {
switch point {
case (let x, 0):
println("x: (x)")
case (0, let y):
println("y: (y)")
case (let x, let y) where x == y:
println("Both x and y: (x)")
case (let x, let y):
println("x: (x), y: (y)")
}
}
printPoint((2,0))
printPoint((3,3))
printPoint((1,2))
// x: 2
// Both x and y: 3
// x: 1, y: 2
The first case is matched because the destructured tuple matches where its
second part is equal. Its first part is set to a constant and made available in
the scope of the case block. The second call matches the third case due to the
additional where
condition being met.
Here’s a solution I came up with in Ruby. The syntax is a bit more involved,
because Ruby doesn’t have the value binding syntactic sugar of Swift:
def print_point(point)
case point
when ->((_,y)) { y.zero? }
puts "x: #{point[0]}"
when ->((x,_)) { x.zero? }
puts "y: #{point[1]}"
when ->((x,y)) { x == y }
puts "Both x and y: #{point[0]}"
else
puts "x: #{point[0]}, y: #{point[1]}"
end
end
print_point([2,0])
print_point([3,3])
print_point([1,2])
# x: 2
# Both x and y: 3
# x: 1, y: 2
This example uses a lambda to match each case. This works because Ruby
overrides Proc’s ===
operator as an alias to call
.
In Ruby, enums aren’t a thing. We’re generally cool with that. We tend to
reach for namespaced constant values to mimic the most basic behavior of enums
as collections of values.
Swift introduces enum
as a value type and allows us to add behavior to them.
An interesting side effect is that enumerations in Swift may be used as state
machines:
enum TrafficLight: String {
case Red = "STOP", Yellow = "WHOA", Green = "GO"
mutating func next() {
switch self {
case Red: self = Green
case Yellow: self = Red
case Green: self = Yellow
}
println("The light has changed...")
}
}
var light = TrafficLight.Green
println("You approach a traffic light...")
println(light.toRaw())
light.next()
println(light.toRaw())
light.next()
println(light.toRaw())
// You approach a traffic light...
// GO
// The light has changed...
// WHOA
// The light has changed...
// STOP
The ability to wrap the state transition logic into a single type is pretty
cool. This type might be used in collaboration with other objects that
observe the current value without care of how and when transitions are made.
Ruby’s lack of “enum” as a concept leads to more code, but it’s not indicative
of a problem per se. I opted to keep the usage as similar to the Swift
implementation as possible, providing class methods for each state. Also, this
implementation is without dependency outside Ruby’s stdlib. It may be worth
checking out the state_machine
gem
amongst others.
class TrafficLight
COLORS = [RED = "STOP", YELLOW = "WHOA", GREEN = "GO"]
def self.Red
new(RED)
end
def self.Yellow
new(YELLOW)
end
def self.Green
new(GREEN)
end
attr_reader :state
def initialize(state)
@state = state
end
def next
@state = case state
when RED then GREEN
when YELLOW then RED
when GREEN then YELLOW
end
puts 'The light has changed...'
end
end
light = TrafficLight.Green
puts 'You approach a traffic light...'
puts light.state
light.next
puts light.state
light.next
puts light.state
# You approach a traffic light...
# GO
# The light has changed...
# WHOA
# The light has changed...
# STOP
“Optional types” are a particularly interesting and important feature of Swift.
Optionals allow Swift to have strong type safety, while also being compatible
with existing Objective-C frameworks that may respond with nil
. Due to their
explicitness, optional types are an arguably more expressive way of indicating
that a value may be missing.
One aspect of optionals does concern me. Some examples the Swift book
illustrate how optionals can be used to ignore chained method calls if any of
the values in the chain are nil:
// assume we have a reference to a `widget` object
let tophat = widget.foo?.bar?.tophat
// say tophat is nil, why?
My warning is that optional chainings enable some nested Law of Demeter
violations. In the above example, there is no way to know what in the chain of
things turned out to be nil, we just know there’s no tophat
in the end.
In the Rails world, this is very similar to the try
method which allows you
to attempt to send messages to an object that might be nil.
# assume `widget` is a valid message in scope
widget.try(:foo).try(:bar).try(:tophat)
# say tophat is nil, why?
I’m not familiar enough with the Apple development ecosystem to make a
statement against optional types in Swift—heck, every variable in Ruby is
“optional.” My point is to highlight the risk for your code to lose confidence
and become tightly coupled to a hierarchy of objects.
Optional types are a tool like anything else. You may build a masterpiece; you
may cut your arm off.
Playing with Swift over the past few weeks has been a lot of fun. Ruby
developers should feel pretty comfortable in the language. A number of modern
syntaxes have been included, which increases its approachability. Apple has made it
pretty clear that things could change drastically before its first release, but
the direction they’re taking it is exciting.
By the way, we teach incredible classes
on this stuff! Check out our offerings
(including Ruby!)
and come learn with me.
Writing documentation is fun—really, really fun. I know some engineers may disagree with me, but as a technical writer, creating quality documentation that will...
Humanity has come a long way in its technological journey. We have reached the cusp of an age in which the concepts we have...
Go 1.18 has finally landed, and with it comes its own flavor of generics. In a previous post, we went over the accepted proposal and dove...