I recently rolled off of a Scala project and onto a Rails project. I noticed that the Rails that I’ve been writing has changed as a result of learning Scala, so I thought it would be interesting to document some of these changes.
The most obvious differences are due to Scala’s static type system. Scala is typed language meaning that all variables have a defined or inferred type, and those types are verified for correctness at compile time. Moving from Scala’s type system back to Ruby’s dynamic types has made me more thoughtful about the types that I am passing around.
In Scala function definitions are often written in the form of:
def add(x: Int, y: Int): Int
X, and y are parameters with a type of Int and Int is also the return type. Now
when using my add method Scala won’t let me use
add with non-integer params.
Building off of this example, if for some reason there was a case where I wanted to add two variables but there was a chance that either of those variables could be nil, Scala has a different way of representing that type, and that’s using the option type. Where an option is either None (similar to Ruby’s nil) or an Int. Now our function would look something like this:
def add(x: Option[Int], y: Option[Int]): Option[Int]
This function definition is now telling us that x or y could be none, or Int. It’s easy in Ruby to forget to think about how your code handles nil cases, whereas Scala forces you to be explicit about when you’re working with a possible None type.
Now if we were looking at how I would write that same function in Ruby, I would try to communicate the type information through my naming:
def optional_addition(optional_int_1, optional_int_2)
The way that I implement
optional_addition would also change. While my Ruby
def add might error when given nil, I would want
def optional_addition to
have some logic for handling that nil case.
Scala’s type system, similar to most typed languages, can produce clearer more reliable code and the more information that can be encoded into the type system the better.
One way to do this in Scala is through the use of case classes. A case class is similar to a model in Rails. They look similar to this:
case class Cat(name: String, color: String, gender: Gender)
Where Gender might be another case class that only allows a select set of ‘gender values’ . Now we know that every instance of cat will have a name, color and gender.
Rails models accomplish similar type guarantees via validations to ensure that attributes and associations are present. Because these are usually only available at the interface with our database, we tend to not have these checks elsewhere in our system. By comparison, in Scala I would create case class for small objects that weren’t necessarily backed by database tables, and were often confined to a small part of my system.
While I’ve seen Rails models that aren’t backed by database tables, it feels clunky to create a model for an object which won’t be used very often. Instead I’ve found myself reaching for Ruby’s structs which don’t have a built in mechanism to ensure data/type integrity, but will guarantee that there are the correct number of attributes.
Struct.new("Cat", :name, :color, :gender)
Struct::Cat.new("Tom", "blue", "female")
To be honest I can’t recall if I had a tendency to favor
.each in Ruby, but
since learning Scala I definitely have a preference for favoring methods such as
.collect. Scala, similar to Ruby, is a mix of functional and
object oriented programming, however the Scala that I’ve been writing has skewed
to being more functional. As such I’ve gravitated towards methods such as
that behave in a more functional way by returning the collection created by
applying the block to each element of the collection. I have been avoiding
methods used only for side effects, like
[1,2,3].each do |b|
b + 1
# Returns: [1,2,3]
[1,2,3].map do |b|
b + 1
# Returns: [2,3,4]
I’m sure that as I write more Rails, and more Scala, I’ll continue to notice differences in the way that I code, these are just my initial observations. I always find it interesting to watch how my coding evolves as I learn new things, and I hope that this was an interesting reflection.