Share
Embed
5 Bullets to Scala Adoption
A talk given at JEEConf 2015 in Kiev, Ukraine.
So you've heard of this newfangled "Scala" thing and think it might be worth checking out. Good for you! Unfortunately, it's also daunting. Your first search for Scala is likely to pitch it as a "statically-typed hybrid object-oriented and functional language", which is technically true but doesn't really help much. Instead, this talk will provide an overview of the language, focusing on five highly practical advantages Scala has over Java without a brain hemorrhage-inducing paradigm shift, followed by some tips and recommendations on Scala adoption in the real world and (as time allows) open-ended Q&A.
Tomer Gabel
May 22, 2015
Transcript
42 slides
-
Agenda The Pitch Ecosystem Community Velocity
Peopleware Yourself Your team Your boss -
Why should you care? Me • System architect at Wix
• Using Scala since 2011 • User group leader: – Underscore (Scala) – Java.IL (Java/JVM) • Organizer of Scalapeño Wix • >60M users • Using Scala since 2012 • The usual suspects: – Microservices – SLAs – Legacy Java codebase -
This is Spar– I mean, Java public class Person {
private String name; private String surname; private int age; public Person(String name, String surname, int age) { this.name = name; this.surname = surname; this.age = age; } public String getName() { return name; } public String getSurname() { return surname; } public int getAge() { return age; } @Override public String toString() { /* … */ } } -
In Java-land public class SimplePersonGenerator {
// Prepare a bunch of useful constants… private static List<String> names; private static List<String> surnames; static { names = new ArrayList<>(); names.add("Jeffrey"); names.add("Walter"); names.add("Donald"); surnames = new ArrayList<>(); surnames.add("Lebowsky"); surnames.add("Sobchak"); surnames.add("Kerabatsos"); } -
In Java-land // Continued from previous slide
private static Random random = new Random(); public static Person generatePerson(int age) { String name = names.get(random.nextInt(names.size())); String surname = surnames.get(random.nextInt(surnames.size())); return new Person(name, surname, age); } public List<Person> generatePeople(int count, int age) { List<Person> people = new ArrayList<>(count); for (int i = 0; i < count; i++) people.add(generatePerson(age)); return Collections.unmodifiableList(people); } -
Scala 101 Java public class Person {
private String name; private String surname; private int age; public Person(String name, String surname, int age) { this.name = name; this.surname = surname; this.age = age; } public String getName() { return name; } public String getSurname() { return surname; } public int getAge() { return age; } @Override public String toString() { /* … */ } } Scala case class Person( name: String, surname: String, age: Int) Provides: – Constructor – Getters – hashCode/equals – toString -
Scala 101 Java public class SimplePersonGenerator {
// Prepare a bunch of useful constants… private static List<String> names; private static List<String> surnames; static { names = new ArrayList<>(); names.add("Jeffrey"); names.add("Walter"); names.add("Donald"); surnames = new ArrayList<>(); surnames.add("Lebowsky"); surnames.add("Sobchak"); surnames.add("Kerabatsos"); } Scala object NaiveScalaPersonGenerator { private val names: List[String] = List( "Jeffrey", "Walter", "Donald" ) private val surnames: List[String] = List( "Lebowsky", "Sobchak", "Kerabatsos" ) First-class singletons -
Scala 101 Java private static Random random = new Random();
public static Person generatePerson(int age) { String name = names.get(random.nextInt(names.size())); String surname = surnames.get(random.nextInt(surnames.size())); return new Person(name, surname, age); } public List<Person> generatePeople( int count, int age) { List<Person> people = new ArrayList<>(count); for (int i = 0; i < count; i++) people.add(generatePerson(age)); return Collections.unmodifiableList(people); } Scala private val random: Random = new Random def generatePerson(age: Int): Person = { val name: String = names(random.nextInt(names.size)) val surname: String = surnames(random.nextInt(surnames.size)) new Person(name, surname, age) } def generatePeople(count: Int, age: Int): List[Person] = { val people: mutable.ListBuffer[Person] = mutable.ListBuffer.empty for (i <-‐ 0 until count) people.append(generatePerson(age)) people.result() } Everything is an expression Comprehensions -
We started off with: object NaiveScalaPersonGenerator {
private val names: List[String] = List("Jeffrey", "Walter", "Donald") private val surnames: List[String] = List("Lebowsky", "Sobchak", "Kerabatsos") private val random: Random = new Random def generatePerson(age: Int): Person = { val name: String = names(random.nextInt(names.size)) val surname: String = surnames(random.nextInt(surnames.size)) new Person(name, surname, age) } def generatePeople(count: Int, age: Int): List[Person] = { val people: mutable.ListBuffer[Person] = mutable.ListBuffer.empty for (i <-‐ 0 until count) people.append(generatePerson(age)) people.result() } } -
Type inference object NaiveScalaPersonGenerator { private val
names: List[String] = List("Jeffrey", "Walter", "Donald") private val surnames: List[String] = List("Lebowsky", "Sobchak", "Kerabatsos") private val random: Random = new Random def generatePerson(age: Int): Person = { val name: String = names(random.nextInt(names.size)) val surname: String = surnames(random.nextInt(surnames.size)) new Person(name, surname, age) } def generatePeople(count: Int, age: Int): List[Person] = { val people: mutable.ListBuffer[Person] = mutable.ListBuffer.empty for (i <-‐ 0 until count) people.append(generatePerson(age)) people.result() } } -
Type inference object NaiveScalaPersonGenerator { private val
names = List("Jeffrey", "Walter", "Donald") private val surnames = List("Lebowsky", "Sobchak", "Kerabatsos") private val random = new Random def generatePerson(age: Int): Person = { val name = names(random.nextInt(names.size)) val surname = surnames(random.nextInt(surnames.size)) new Person(name, surname, age) } def generatePeople(count: Int, age: Int): List[Person] = { val people = mutable.ListBuffer.empty for (i <-‐ 0 until count) people.append(generatePerson(age)) people.result() } } -
Collections // Some data to start with… val people
= generatePeople(5, 42) val names = people.map(p => p.name) val adults = people.filter(p => p.age >= 18) // Functions are 1st class citizens def isAdult(p: Person) = p.age >= 18 val adults2 = people.filter(isAdult) assert(adults2 == adults) -
Collections val youngest = people.minBy(p => p.age) val
oldest = people.maxBy(p => p.age) val hasAdults = people.exists(p => p.age >= 18) val (adults, minors) = people.partition(p => p.age >= 18) val randomPairs = Random.shuffle(people).grouped(2) -
Collections val trivial: Map[String, String] = Map("name"
-‐> "Jeffrey Lebowsky", "alias" -‐> "The Dude”) val directory: Map[Char, List[Person]] = people .groupBy(p => p.surname.head.toUpper) .withDefaultValue(List.empty) val beginningWithK: List[Person] = directory('K’) val countByFirstLetter: Map[Char, Int] = directory.mapValues(list => list.size) -
Options case class PartiallyKnownPerson(name: Option[String] = None,
surname: Option[String] = None, age: Option[Int] = None) val person = PartiallyKnownPerson(name = Some("Brandt")) val explicitAccess = person.age.get // <-‐-‐ NoSuchElementException thrown here val withDefault = person.name.getOrElse("Anonymous") println("Hello " + withDefault + "!") // Options are also zero-‐ or single-‐element collections! person.name foreach { n => println("hello" + n) } Named Parameters -
Traits import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class
ClassWithLogs { private static Logger log = LoggerFactory.getLogger(ClassWithLogs.class); public String getNormalizedName(Person person) { log.info("getNormalizedName called"); log.debug("Normalizing " + person.toString()); String normalizedName = person.getName().toUpperCase().trim(); log.debug("Normalized name is: " + normalizedName); return normalizedName; } } Eager Evaluation Boilerplate -
Traits Usage class ClassWithLogs extends Logging {
def normalizeName(person: Person) = { logInfo("normalizeName called") logDebug("Normalizing " + person) val result = person.name.toUpperCase.trim logDebug("Normalized: " + result) result } } Definition trait Logging { private val log = LoggerFactory.getLogger(this.getClass) protected def logInfo(message: =>String) = if (log.isInfoEnabled) log.info(message) protected def logDebug(message: =>String) = if (log.isDebugEnabled) log.debug(message) // ... } Lazy Evaluation -
DSLs class StackTest extends Specification { "Seq.head"
should { "throw an exception if empty" in { Seq.empty[String].head should throwA[NoSuchElementException] } "return the first element" in { Seq(1, 2, 3).head shouldEqual 1 } } } -
DSLs val orders = List[Order]( new Order to
buy(100 sharesOf "IBM") maxUnitPrice 300 using premiumPricing, new Order to sell(200 bondsOf "Sun") maxUnitPrice 300 using { (qty, unit) => qty * unit – 500 } ) class StackTest extends Specification { "Seq.head" should { "throw an exception if empty" in { Seq.empty[String].head should throwA[NoSuchElementException] } "return the first element" in { Seq(1, 2, 3).head shouldEqual 1 } } } -
What about Java 8, you ask? • Java 8 is
awesome • It validates Scala! • But it’s not up to par – Closures/lambdas – Default methods -
Note that we haven’t covered… • For comprehensions • Lazy
vals • Tuples • Generics – Type bounds – Variance – Higher-kinded types • Implicits – Type classes – Conversions – Parameters • Functions • Top and bottom types • Scoped imports • Extractor objects • Structural types • Type members • Path-dependent types • Futures and promises • Macros -
Not just servers! JavaScript Android • Two major libraries: Macroid
and Scaloid • Both provide: – DSL for layouts – Interop with Java code – sbt integration • Overhead: 10-500KB • JavaScript backend provided by Scala.js – Non-experimental – Bidirectional interop – Source maps – Output ranges 170-400KB -
User groups • ScalaUA – Ukrainian user group, founded in 2011
– 250 members http://scala-lang.org.ua/ https://www.facebook.com/groups/scala.ua/ -
Step Up Your Game • New tech is scary –
Migration path – The dreaded rewrite – Impedance mismatch – Onboarding • It’s all about risk -
1. Gateway Drug • Scala rocks your tests! – Expressive
syntax – DSL support – Excellent libraries • Trivial integration – Ant/Maven/Gradle… – Non-production code! class StackTest extends Specification { "Seq.head" should { "throw an exception if empty" in { Seq.empty[Any].head should throwA[NoSuchElementException] } "return the first element" in { Seq(1, 2, 3).head shouldEqual 1 } } } -
2. Greenfield project • Applicable for: – New projects –
Self-contained – Experimental/non- critical • Assume 3-4 weeks’ overhead -
3. The layered approach Components Frameworks Deployment Executable Container Crawler
framework Twitter TripAdvisor OpenTable Web framework Dashboard Backoffice -
4. The silo approach Components Frameworks Deployment Executable Container Crawler
framework Twitter TripAdvisor OpenTable Web framework Dashboard Backoffice -
Pilot projects “A pilot project is one in which you…
try some new and unproved technique… you can expect to be inefficient at the start. On the other side… is the improvement in productivity... [and] the Hawthorne Effect.” - Chapter 24, “Peopleware” -
The Hawthorne Effect “A type of reactivity in which individuals
improve an aspect of their behavior in response to their awareness of being observed” - Wikipedia -
The Hawthorne Effect “A type of reactivity in which individuals
improve an aspect of their behavior in response to their awareness of being observed” - Wikipedia “Uncontrolled novelty effects cause on average 30% of a standard deviation (SD) rise” - Clark & Sugrue "Research on instructional media, 1978-1988”