Introduction to Scala for Rubyists
This blog provides Rubyists with an easy introduction to the Scala programming language. It will show you how to write Ruby-like code in Scala. As you get more familiar with Scala, you can slowly transition from the Ruby way to the Scala way.
Learning a new language is psychologically painful. Learning Scala the Ruby way limits the pain of the transition — let’s dive in!
Variable assignment
Ruby is a dynamically typed language, so variables can be assigned to an object of one type, and then reassigned to an object of a different type.
x = 99
x = "cool"
x = ["one", "two"]
Scala is statically typed so once a variable is assigned, it can only be reassigned to other variables of that type.
var coolNumber = 8
coolNumber = 10 // this works
coolNumber = "phil" // error: type mismatch
Scala also supports immutable references, which are variables that cannot be reassigned, even to other objects of the same type. The val
keyword is used to make immutable references.
val myName = "matthew"
myName = "mateo" // error: reassignment to val
Scala variables with the Any
type can be reassigned to values of any type and behave a lot like Ruby variables.
var zz: Any = 66
zz = List(1, 10)
zz = "crazytown"
Experienced Scala programmers don’t use var
and Any
in this manner, but it’s fine when you’re just getting started!
Arrays
Ruby is dynamically typed, so arrays can contain different types of objects.
random_stuff = ["cat", {}, 77]
Scala has several types of Array-like data structures. We’ll only talk about the List data structure in this article. Other Scala guides would take this opportunity to go off on a huge tangent on the different types of arrays in Scala, but we’ll refrain — you’re welcome ;)
Scala is statically typed, so lists generally need to contain objects of the same type.
val negNumbers = List(-10, -20) // this works
We can use the Any
workaround again to create lists with different types of objects.
val weirdStuff: List[Any] = List("blade", 99, "glitter")
Array mutability
Ruby arrays are mutable, so elements can be added or removed.
best_friends = ["toby", "jim"]
best_friends.push("stanley")
best_friends.shift
Scala lists aren’t mutable, but you can create a new list by unioning two lists.
val englishNames = List("matthew", "powers")
val colombiaNames = List("mateo", "poderes")
val allNames = englishNames ++ colombiaNames
allNames // List("matthew", "powers", "mateo", "poderes")
Scala has mutable array data structures, but we’re not going to dig into those right now. We’re avoiding the Scala rabbit holes.
Collection Methods
Ruby borrowed methods like map, filter, and inject from functional programming languages to make it easier to work with collections.
words = ["cAt", "sEAt", "cRAzY"]
words.map { |w| w.downcase } // ["cat", "seat", "crazy"]
Ruby also provides symbol to proc syntactic sugar to allow for a more concise syntax.
words.map(&:downcase) // ["cat", "seat", "crazy"]
Scala also provides a map
method for lists.
val movies = List("thOR", "UP", "HeR")
movies.map((movie: String) => movie.toLowerCase())
Scala provides some syntactic sugar with the underscore character.
movies.map(_.toLowerCase())
Classes
Typical Ruby classes use mutable instance variables to maintain state.
class Person
attr_accessor :first_name, :last_name def initialize(first_name, last_name)
@first_name = first_name
@last_name = last_name
end def full_name
"#{first_name} #{last_name}"
end
endbob = Person.new("bob", "loblaw")
bob.full_name() # "bob loblaw"# Instance variables allow the object to be mutated.
bob.last_name = "barker"
bob.full_name # "bob barker"
Let’s create a similar Scala class that is instantiated with variables and creates mutable objects.
class Person(var firstName: String, var lastName: String) {
def fullName(): String = {
s”$firstName $lastName”
}
}val phil = new Person("phil", "helmuth")
phil.fullName() // "phil helmuth"// the phil object is mutable
phil.lastName = "mickelson"
phil.fullName() // "phil mickelson"
Scala programmers prefer immutable objects, but it’s fine to write code like this when you’re getting started.
Singleton methods and companion objects
Ruby classes can define both singleton methods and instance methods.
class Nintendo # singleton method
def self.about
"we make fun games"
end # instance method
def make_something
"we’re building"
endend# call the singleton method
Nintendo.about # "we make fun games"# call the instance method
n = Nintendo.new
n.make_something # "we’re building"
Scala companion objects allow for behavior that’s similar to singleton methods.
class Nintendo {
def makeSomething(): String = {
"we’re building"
}
}object Nintendo {
def about(): String = {
"we make fun games"
}
}Nintendo.about() // "we make fun games"val n = new Nintendo
n.makeSomething() // "we’re building"
The class Nintendo
and object Nintendo
definitions must be in the same file for the Scala compiler to interpret object Nintendo
as a companion object. Otherwise, the compiler will interpret the object definition as a namespace collision.
Modules and Traits
Ruby uses modules to add instance methods to a class.
module CatEnthusiast
def cat_feelings
"I LOVE CATS!"
end
endclass Student
include CatEnthusiast
def hobby
"sleep"
end
ends = Student.new
s.cat_feelings # "I LOVE CATS!"
Traits in Scala are similar to modules in Ruby.
trait CatEnthusiast {
def catFeelings(): String = {
"I LOVE CATS!"
}
}class Student extends CatEnthusiast {
def hobby(): String = {
"sleep"
}
}val s = new Student
s.catFeelings() // "I LOVE CATS!"
Nesting modules vs. packages
Ruby classes are organized in a nested module structure that mimics the directory structure.
For example, the lib/turf/lookup.rb
file looks like this in the Turf open source project:
module Turf
class Lookup
## code here
end
end
Scala classes are located in a nested package structure that follows the directory structure.
Here’s what the src/main/scala/com/github/mrpowers/spark/daria/sql/SparkSessionExt.scala
file looks like in the spark-daria open source project.
package com.github.mrpowers.spark.daria.sqlobject SparkSessionExt {
// code goes here
}
Scala follows some crazy Java directory structure conventions, so code is often nested deeply inside empty directories. If you’re completely new to the Java / Scala ecosystem, the conventional directory structure will seem ridiculous. Thankfully Scala packages provide a clean way to navigate the maze of empty directories. Deeply nesting Ruby modules gets clunky and Scala’s package notation really shines in comparison.
Monkey patching
Ruby makes it easy to add methods to existing classes with a process called monkey patching.
class String
def funny_joke
"Good luck debugging this code!"
end
end"hi".funny_joke # "Good luck debugging this code!"
Scala allows for monkey patching with implicit classes.
object StringExt {
implicit class StringMethods(s: String) {
def stillUgly(): String = {
"Yuck, except awesome sometimes"
}
}
}import StringExt._
"hello".stillUgly() // "Yuck, except awesome sometimes"
Next Steps
Jason Swartz wrote an awesome Scala book that’s approachable for Ruby developers.
I’ll write another blog post covering more advanced language features soon :)