Iamvery

The musings of a nerd


Discover Swift with this One Weird Rubyist!

— Aug 12, 2014

Originally posted on the Big Nerd Ranch Blog.


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.

Getting Started

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.

Swift CLI

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.

thumbs up

Constant Change

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

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.

Value Collections

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.

Destructured Assignment

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

Value Bindings

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.

Enums as a State Machine

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

Consider Demeter

“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.

Always Learning

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.

comments powered by Disqus