ADOPTING SCALA IN JAVA TEAMS - Learn Scala for Java Developers (2015)

Learn Scala for Java Developers (2015)

IV. BEYOND JAVA TO SCALA

You might be wondering how to adopt Scala into your existing team. The good news is that you don’t need to wait for that greenfield project to start. With a bit of planning, you can integrate Scala into your existing Java projects.

In this part of the book, I’ll report on some of my experiences moving existing Java projects to Scala. We’ll talk about what you can expect if you try to do so. I’ll outline a typical learning curve and give you one or two things to look out for, some concrete things you should be doing, and what you should be avoiding.

Adopting Scala

Avoid Not Enough

I’d been working with Java for more than ten years before I starting looking at Scala properly. My first experience was a toe in the water. The team was naturally sceptical, so we decided to section off a part of the existing codebase to try Scala. We converted our tests to Scala and left mainline development in Java.

In hindsight, this was a terrible idea. By working only with test code, there wasn’t enough critical mass or momentum to improve. The kinds of problems we were solving in test code were pretty well understood and the existing testing frameworks (like JUnit) solved most of them for us.

There didn’t seem to be much scope to really use the language features. We pretty much just rewrote the tests in another testing framework which happened to be in Scala. We didn’t learn much at all and in the end, reverted everything back to Java. It was a total waste of time and effort.

The one thing I learnt from this was that to explore a new language, you need real problems to solve: design problems, domain problems, business problems. Sectioning off a subset of your codebase limits the kinds of problems you can explore and so limits your learning.

Don’t Do Too Much

The next time I had a chance to try out Scala, I went to the opposite extreme. I jumped in at the deep end, worked exclusively with Scala, and tried really hard to adopt functional programming, new patterns, new architectural designs… Anything I could find that was related to Scala.

I hadn’t done much functional programming before so pretty quickly I hit a wall. I was faced with concepts and ideas that were completely foreign to me. I struggled, as I was trying to learn too much, too soon: new frameworks, libraries and techniques, as well as a new language. I tried to run before I could walk.

It took a while for me to realise that, despite being an experienced developer, I was actually a novice when it came to Scala and functional programming. I’d failed to recognise this and in hindsight it was irresponsible to commit to building software like this. I was hired for my expertise but managed to put barriers up, preventing me from applying that expertise.

Purely Functional FTW?

Later on I was fortunate enough to work with some really experienced developers, all well-versed in functional programming and mostly coming from a Haskell background. This was great as I had the chance to benefit from others’ experiences, and the learning curve got a little easier. After a while, the team started to adopt really advanced ideas and it soon became apparent that there was another wall to get past. We started to talk about much deeper application concerns and whether we could solve these functionally.

My point here is that for teams not used to it, heavyweight functional programming ideas can be pretty exotic. Even in industry, I think it’s fair to say that there are very few teams fully embracing this style.

I don’t feel like, collectively, we can say if this extreme is helping to solve real-world business problems or not. It’s just another way of doing things. It does represent a very different approach to building software from the mainstream and for us, it was perhaps a little too much. It caused a bit of a divide in the team where some people were comfortable experimenting with this approach and others weren’t.

What to Expect

The Learning Curve

If you’ve just started to learn Scala and are wondering what to expect, it’s typical to experience a quick ramp-up in skill followed by a slower adoption of the more sophisticated features. In this chapter, I talk about what I think of as a typical learning curve.

Based on my experiences and talking to various Scala teams, we can chart a typical Scala learning curve like this, with experience (or time) on the x axis and some measure of “learning” on the y.

Fig. 4.1. Typical Scala adoption as a learning curve.

Fig. 4.1. Typical Scala adoption as a learning curve.

Milestone 1: OO in Scala

When you first start, you can expect getting up to speed with the language to be a fairly steep incline.

“Steep” but also “short”: it’s not difficult to get to the first plateau, so you can expect a relatively quick increment in learning.

You’ll probably sit here for a bit applying what you’ve learnt. I see this as the first milestone: to be able to build object-oriented or imperative applications using language-specific constructs and features, but without necessarily adopting functional programming. It’s just like learning any other language in the Java / C family.

Milestone 2: FP in the small, OO in the large

The next milestone involves adopting functional programming techniques.

This is a much more challenging step, and likely to be a shallower curve. Typically this will involve using traditional architecture design but implementing functional programming techniques in the small. You can think of this approach as “functional in the small, OO in the large”. Starting to embrace a new functional way of thinking and unlearning some of the traditional techniques can be hard, hence the shallower incline.

Concrete examples here are more than just language syntax: things like higher-order and pure functions, referential transparency, immutability and side effect–free, more declarative coding; all the things that are typically offered by pure functional languages. The key thing here is that they’re applied in small, isolated areas.

Milestone 3: FP in the large

The next challenge is working towards a more cohesive functional design; this really means adopting a functional style at a system level; architecting the entire application as functions and abandoning object-oriented style completely. So, aiming for something like a Haskell application.

All the concrete functional programming mechanisms above apply but this time, throughout the system; not to isolated areas but lifted to application-wide concerns. Picking up advanced libraries like Scalaz goes hand-in-hand with this part of the curve.

The Learning Continuum

You can also think of adoption of Scala as more of a continuum, with traditional imperative programming on the left and pure functional programming on the right.

Fig. 4.2. Imperative (Java) to pure functional programming (Haskell) as a continuum.

Fig. 4.2. Imperative (Java) to pure functional programming (Haskell) as a continuum.

You can think of the far right as Haskell on the JVM. Haskell is a pure functional language so you don’t have any choice but to design your app in a functional way. Scala is an object-oriented / functional hybrid; it can only give you the tools. It can’t enforce functional programming; you need discipline and experience in Scala to avoid, for example, mutating state, whereas Haskell will actually stop you.

As you start out on the continuum using Java and move to the right, libraries like Functional Java, Totally Lazy and even Java 8 features will help you adopt a more functional style. There comes a point where a language switch helps even more. Functional idioms become a language featurerather than a library feature. The syntactical sugar of for comprehensions are a good example.

As you carry on, using libraries like Scalaz makes it easier to progress towards pure functional programming, but remember that reaching the far right, or the top-right quadrant of the learning curve, isn’t the goal in and of itself. There are plenty of teams operating effectively across the continuum.

When you’re adopting Scala, make a deliberate decision about where you want to be on the continuum, be clear about why, and use my learning curve as a way to gauge your progress.

Goals

Reaching the purely functional milestone is going to be difficult. It may not even be the right thing to do for your team. A purely functional system isn’t necessarily better; I suggest that most Java teams trying to adopt Scala should aim for somewhere between Milestones 1 and 2, somewhere towards the middle of the continuum.

I think this is a good balance between seeing the benefits of the language and taking on too much. If you’re working in an commercial environment, you still have to deliver software. Remember that you’re potentially trading your experienced developers for novices as you move to the right. It may be better to balance your delivery commitments with your learning, since delivery risk goes up as you go the right.

Tips

Reflecting on my experiences I have three major tips.

1. Be clear about what you want from Scala.

2. Get guidance and share experiences.

3. Be deliberate and have a plan.

Be Clear

· Be clear what you want out of it. You should be asking what’s in it for you. If you’re going to use Scala, you should be able to explain why. Do you think being more concise will help you? Is it the benefits of functional programming? Immutability? How are you going to measure it? Will the whole team aim for a pure functional programming style like Haskell? Or OO? Decide which up front. Agree.

· Understand the team dynamic. Understand your team and what their wants and needs are. Gauge the team’s appetite and goals. Ultimately, everyone needs to buy into the idea and head in the same direction. If one or two people feel left out or pull in another direction, you’ll have problems further down the line.

· Set expectations. Talk to the team and management. Explain the risks and share your motivations and expectations. To set your expectations: learning full-on functional programming (think Haskell) is hard. Be prepared.

· Review. Once you’ve started, keep reviewing where you are. Are you getting out of it what you thought you would? Can you prove or disprove any of the assumptions you started with? Is the business benefiting? If you can be more quantitative and use measurements, you can be more objective. And don’t be afraid to change your mind. Maybe you discover that it’s not the right choice for the team. You can always back out.

Get Guidance

· Hire an expert. This is my single biggest tip. If you have a real expert on the team, they can guide you through the syntax and features, and more importantly the adoption process. They can hold you back when you shouldn’t be racing and open the door to new techniques when you’re ready. It can be hard to find genuine experts but they can be worth their weight in gold.

· Be active in the community. Attend events, conferences, meetups. Create a community. Learn from others and share your own experiences.

· Look at open source. But not too much. It’s good to see how others have done things — I’ve certainly been exposed to some of the more exotic syntax via open source — but bear in mind that what’s right for them may not be right for you. Take it with a pinch of salt. Use other people’s code to inspire learning but don’t copy it.

Have a Plan

· Have a plan. Don’t just start your next project in Scala; be deliberate and have a plan.

· Think about your goals. Write them down, then sketch out the steps that will take you closer to you them. For example, if a goal is to get up to speed fast, you may want to run some lunchtime knowledge-sharing sessions with your team.

· Decide where on the functional programming continuum you want to be. Do you want to jump in at the deep end or adopt functional idioms later? One tip to minimise the learning curve is to avoid using native Scala libraries to start with. They often lead you into functional style and at the end of the day, it’s another API to learn. You can always stick with the Java libraries you know, at least to start with.

· Make a commitment. Commit to your goals; don’t do what I did and sideline Scala to the tests. Actively decide on Scala and make a personal commitment to see it happen.

If you follow these tips, you should be clear about your goals and expectations, have an idea where you’ll get guidance and have planned out your next steps. You’ll be facing the prospect of working with a Scala codebase, probably converting Java code and working with new tools and libraries.

In the next chapters we’ll look at some practical tips on how to proceed, including converting Java code and managing your codebase.

Convert Your Codebase

At this point, you’re likely to have an existing Java codebase and may be wondering if you should try and convert the Java to Scala.

It may be a lot of effort, but I say it’s definitely worth investing in converting the entire codebase to Scala if you’re serious about adopting Scala. Don’t aim to leave half and half. Accept that it’s going to take some time and effort but commit to achieving it. If you don’t finish the job, apathy can set in, not to mention that inter-op between old Java and Scala can get really awkward. You’ll make your life easier if you see the job through.

IntelliJ IDEA has a convert function which is a great starting place. It’ll let you quickly turn a Java class into Scala, but be prepared: it’s not often idiomatic and may not even compile without coercion. Treat any automated conversion as a learning exercise. Don’t accept the transformation at face value, but look at the results and apply your judgement to tidy up. For example, rather than create a case class, IntelliJ often creates fields within the Scala class. You’ll usually be able to rephrase a verbatim conversion in a more Scala-savvy way.

Consider your build tools. These will need to be updated. SBT and Maven both support mixed Java/Scala projects so there’s no excuse not to keep running your continuous integration and test environments. You might also want to consider whether it’s worth porting over your existing build to SBT.

Manage Your Codebase

Once you have your newly converted Scala codebase, you’ll be working with it for a while. Here are some practicalities you should be aware of and some general tips to help manage your codebase.

Conventions

· There are so many syntax variations and conventions that the sheer number of syntax options available can be intimidating. Settle on your own conventions.

· Find a way of doing things that works for your team and stick to it. Avoid context switching between syntax modes to start with. That might mean using pattern matching rather than using map to start with. Wait until everyone is comfortable before switching over.

· Review these choices regularly as you don’t want to be held back once you’re up and running.

· Have a look at Twitter’s efforts as an example. They’ve managed to condense their experiences into a really practical guide. Well worth a read.

· Typesafe have a style guide which is worth reading over but should be taken with a pinch of salt.

What to Avoid

It’s also great to know what to avoid when you’re first starting out. I wish I knew some of these things before I started:

· SBT can make your life harder. SBT starts off very simple but if you want to do anything sophisticated, it gets fairly taxing. Be prepared to do some learning; SBT isn’t something that you can get very far with by cutting and pasting examples from the internet. You do actually need some understanding. I think you’ll have bigger things to worry about so my suggestion is to stick with a build tool that you’re familiar with to start with. It’s very easy to support Scala projects in Maven.

· Scalaz is not the place to start. This is the Haskell programmer’s library for Scala. It’s hardcore and if you’ve not been doing purely functional programming for years, just plain avoid it. You don’t need to know what an applicative functor is or why you’d use a Kleisli. At least, not to start with.

· “Fancy” Scala libraries might not help. When Scala first came out, a bunch of open source libraries hit the web. People got carried away and in hindsight would probably admit they went a little overboard with the syntax. As a result there are lots of APIs that were not designed very well:

o Dispatch The http library Dispatch is a good example. The method to add a query parameter to a request is <<?. That just doesn’t read as intuitively as addRequestParameter. In fairness, Dispatch offers both variants but the upshot is to be skeptical of libraries that overuse operator overloading.

o Anorm is another one that offers a parser API to extract SQL results. It basically parses ResultSets but it has such a unintuitive DSL for doing so, it makes you wonder what was wrong with doing it manually.

· Java libraries work just fine. You can always use the Java libraries you know and love from Scala.

· Lack of consistency is dangerous. We’ve already touched on this: don’t try and use all the syntax variations at once. Figure out one properly before moving on. Keep things consistent.

Other Challenges

You’ll face plenty of other challenges:

· Compilation speed is slow. It may or may not be something that concerns you but be aware: it’s unlikely to match Java’s speed anytime soon. In part this is due to just how sophisticated the Scala compiler is; it does a lot of work and that comes at a price.

· Keeping your IDE build configuration in sync with your external build tool can be a challenge. Maven is so mature now that its IDE plugins pretty much handle everything for you, but the SBT plugin for IDEA on the other hand has some problems. There are third-party tools that generate IDE project files but your mileage may vary. The best I can offer is to pick the tools that seem to have the most weight behind them for your IDE.

· We’ve already talked about the functional programming continuum but it’s worth mentioning again that it’s a good idea to be mindful of where you want to be. I recommend aiming for “Functional in the small, OO in the large” to start. You’ll need to continually monitor your progress and embrace the next learning challenge when you feel ready. Push the boundaries and keep learning.

Syntax Cheat Sheet

Values

val x: Int = 42

var y: String = "mutable"

val z = "Scala FTW!" // using type inference

Functions

def add(x: Int, y: Int): Int = x + y // single expression

def add(x: Int, y: Int) { // without =, Unit is returned

x + y

}

def min(x: Int, y: Int): Int = { // last line = return value

if (x < y)

x

else

y

def min(x: Int, y: Int): Int = if (x < y) x else y

def min(x: Int, y: Int): Int = { // but don't forget the else

if (x < y)

x

y // bug!

def add(x: Int, y: Int) = x + y // return types can be inferred

def add(x: Int, y: Int = 2) = x + y // default y to 2 if omitted

val add = (x: Int, y: Int) => x + y // anonymous function

add(4, 2) // call as usual

// convert an anonymous class (Ordering in this case) to a lambda

implicit def functionToOrdering[A](f: (A, A) => Int): Ordering[A] = {

new Ordering[A] {

def compare(a: A, b: A) = f.apply(a, b)

}

}

Call-by-Name

def runInThread(task: => Int) {

new Thread() {

override def run(): Unit = task

}.start()

}

Operator Overloading and Infix

5 * 10 // same as 5.*(10)

"Scala" replace("a", "*")

Classes

class Customer // create a public class

class Customer(name: String) // with primary ctor

class Customer private(name: String) // private constructor

private class Customer // a private class

private class Customer private(name: String) // with private ctor

val c = new Customer("Bob") // create an instance

// class with primary constructor and auxiliary constructor

class Customer(forename: String, surname: String) {

def this(surname: String) {

this("Unknown", surname)

}

}

Case Classes

case class Customer(name: String) // a case class

case class Customer(val name: String) // val is redundant here

// no need to use "new" to create a new instance with case classes

Customer("Bob")

// equality and hash code are free with case classes:

new Customer("Bob") == new Customer("Bob") // returns true \

Singleton Object

// a singleton instance

// when paired with a class, the object becomes a "companion" object

object Customer

Inheritance

class B extends A // subtype inheritance

class C(x: Int)

class D(value: Int) extends A(value) // calling the super constructor

// overriding methods ("override" needed overriding non-abstract methods)

class E {

def position: Int = 5

}

class F extends E {

override position: Int = super.position + 1

}

// you can mixin in multiple traits but only one class (A)

trait SelfDescribing

trait Writable

class B extends A with SelfDescribing with Writable

class B extends SelfDescribing with A with Writable // compiler failure

Fields

// name is not a field, it's available to the primary constructor only

class Customer(name: String)

// name is a public field (getter, no setter created)

class Customer(val name: String)

// name is a public field (getter and setter created)

class Customer(var name: String)

// name is a private field (a private getter is generated but no setter)

class Customer(private val name: String)

// name is private (a private getter and setter is generated)

class Customer(private var name: String)

customer.name_=("Bob") // you can call the setter method directly

customer.name = "Bob" // or use infix notation

Collections

val list = List(1, 4, 234, 12) // create a list

val map = Map(1 -> "a", 2 -> "b") // create a map

list.foreach(value => println(value))

list.foreach(println)

for (value <- list) println(value)

String Formatting

// String interpolation replaces '$' tokens with values

s"Customer name: $name USD" // Customer name: Bob

// Expression require additional parentheses

s"Customer basket value is $(customer.basket.value) USD"

// The 'f' prefix is like String.format

f"Square of 42 is ${math.sqrt(42)}%1.2f" // Square of 42 is 6.48

"Escaping \"quotes\" is the same as in Java"

// triple quotes let you span rows and include quotes unescaped

""""vCard": {

"id" : "007",

"name" : "bond",

"address" : "MI5"

}"""

// The 'raw' string interpolator doesn't escape the usual escape chars

raw"a\nb" // a\nb

Apply Method

val bob = Customer.apply("Bob") // use as factory methods

val bob = Customer("Bob") // no need to call explicitly

val array = Array(1, 54, 23, 545, 23)

array.apply(0) // apply on array is a "getter"

array(0) // result is "1"

Update Method

val array = Array(1, 54, 23, 545, 23)

array(0) // result is "1"

array.update(0, 34)

array(0) // result is "34"

array(0) = 55 // = is a shortcut to update

array(0) // result is "55"

Pattern Matching

value match {

case 'R' => ... // literal match

case Customer(name) => ... // constructor match (case classes)

case x: Int => ... // type query match (ie instance of)

case x: Int if x > 5 => ... // with guard condition

case (x, y) => ... // deconstruction using unapply

case _ => ... // default case

}

// same syntax is used with try/catch and exceptions

try {

// ...

} catch {

case e: MalformedURLException => println("Bad URL")

case e: IOException => println(e.getMessage)

}