Scala in Places API

22
Scala in Nokia Places API Lukasz Balamut - Places API Team 17 April 2013

description

talk to the Scala User group in Berlin

Transcript of Scala in Places API

Page 1: Scala in Places API

Scala in Nokia Places API

Lukasz Balamut - Places API Team

17 April 2013

Page 2: Scala in Places API

Purpose of this talk

I Why are we spending time migrating to a new language?I How are we doing that?I What have we learned?

and of course: we are hiring.

Page 3: Scala in Places API

Our scala story.

Our migration in lines of code

We’ve discovered more and more things on the way.

Page 4: Scala in Places API

The Concordion tests problemIn June 2011 we had problems with Concordion (HTML based acceptance testframework),

I hard to integrateI HTML tests were not readable

Solution:

I Multi-line Strings in ScalaI Continue using JUnit

val expectedBody = """

{"id":"250u09wh-83d4c5479b7c4db9836602936dbc8cb8",

"title" : "Marfil (Le)",

"alternativeNames" : [

{"name" : "Le Marfil",

"language" : "fr"

}]

}"""

Page 5: Scala in Places API

The test names problem

I JUnit + camel case - not that readable for long names

@Test

def preservesSearchResponseItemOrder() { /* ... */ }

I JUnit + underscores - violates naming convention, but better to read

@Test

def preserves_search_response_item_order() { /* ... */ }

I ScalaTest

test("preserves search response’s item order") { /* ... */ }

It is supported by tools we use and produces nice output when run (as a bonus)

[info] DiscoverAroundAcceptanceTest:

[info] - calls nsp with 15Km radius

[info] - preserves search response’s item order

[info] - when requesting zero results, no request to search is made

Page 6: Scala in Places API

Lift-Json

I JSON (de)serialisation can be done in really nice way

e.g. getting place id:

{"place": {

"a_id": "276u33db-6f084959f97c45bc9db26348bafd4563"

}}

Also there is a generic way of parsing json, that gives you XPath like access:

val id = (json \ "place" \ "a_id").extract[String]

I Lift-json provides also easy and efficient case class JSON (de)serialisation,hance case classes

Page 7: Scala in Places API

Scala case classes in the modelWe model our upstream and downstream datamodels as case classes.

case class Place (

name: String,

placeId: PlaceID

//(...)

)

I Each case class is a proper immutable data type - the new Java Bean ;),I A lot of boilerplate code is generated by the compiler e.g.:

I accessors,I equals/hashCode,I copy methods,I toStringI apply/unapply (for pattern maching),

After decompiling the scala code above we will end up with 191 lines of Java:

package pbapi.model.api;

import scala.*;

import scala.collection.Iterator;

import scala.runtime.BoxesRunTime;

import scala.runtime.ScalaRunTime$;

public class Place

implements Product, Serializable

{

public static final Function1 tupled() {return Place$.MODULE$.tupled();

}

public static final Function1 curry() {return Place$.MODULE$.curry();

}

public static final Function1 curried() {return Place$.MODULE$.curried();

}

public Iterator productIterator() {return scala.Product.class.productIterator(this);

}

public Iterator productElements() {return scala.Product.class.productElements(this);

}

public String name() {return name;

}

public PlaceID placeId() {return placeId;

}

public Place copy(String name, PlaceID placeId) {return new Place(name, placeId);

}

public PlaceID copy$default$2() {return placeId();

}

public String copy$default$1() {return name();

}

public int hashCode() {return ScalaRunTime$.MODULE$._hashCode(this);

}

public String toString() {return ScalaRunTime$.MODULE$._toString(this);

}

public boolean equals(Object obj) {if(this == obj) goto _L2; else goto _L1

_L1:

Object obj1 = obj;

if(!(obj1 instanceof Place)) goto _L4; else goto _L3

_L3:

String name$6;

PlaceID placeId$4;

Place place1 = (Place)obj1;

String s = place1.name();

PlaceID placeid = place1.placeId();

name$6 = s;

placeId$4 = placeid;

if(gd22$1(name$6, placeId$4) ? ((Place)obj).canEqual(this) : false) goto _L2; else goto _L5

_L4:

if(false) goto _L2; else goto _L5

_L2:

true;

goto _L6

_L5:

false;

_L6:

return;

}

public String productPrefix() {return "Place";

}

public int productArity() {return 2;

}

public Object productElement(int i) {int j = i;

j;

JVM INSTR tableswitch 0 1: default 24

// 0 39

// 1 46;

goto _L1 _L2 _L3

_L1:

throw new IndexOutOfBoundsException(BoxesRunTime.boxToInteger(i).toString());

_L2:

name();

goto _L4

_L3:

placeId();

_L4:

return;

}

public boolean canEqual(Object obj) {return obj instanceof Place;

}

private final boolean gd22$1(String s, PlaceID placeid) {s;

String s1 = name();

if(s != null) goto _L2; else goto _L1

_L1:

JVM INSTR pop ;

if(s1 == null) goto _L4; else goto _L3

_L2:

s1;

equals();

JVM INSTR ifeq 57;

goto _L4 _L3

_L4:

placeid;

PlaceID placeid1 = placeId();

if(placeid != null) goto _L6; else goto _L5

_L5:

JVM INSTR pop ;

if(placeid1 == null) goto _L7; else goto _L3

_L6:

placeid1;

equals();

JVM INSTR ifeq 57;

goto _L7 _L3

_L7:

true;

goto _L8

_L3:

false;

_L8:

return;

}

public Place(String name, PlaceID placeId) {this.name = name;

this.placeId = placeId;

super();

scala.Product.class.$init$(this);

}

private final String name;

private final PlaceID placeId;

}

package pbapi.model.api;

import scala.*;

import scala.runtime.AbstractFunction2;

public final class Place$ extends AbstractFunction2

implements ScalaObject, Serializable

{

public final String toString() {return "Place";

}

public Option unapply(Place x$0) {return ((Option) (x$0 != null ? new Some(new Tuple2(x$0.name(), x$0.placeId())) : None$.MODULE$));

}

public Place apply(String name, PlaceID placeId) {return new Place(name, placeId);

}

public Object readResolve() {return MODULE$;

}

private Place$() {}

public static final Place$ MODULE$ = this;

static {new Place$();

}}

Page 8: Scala in Places API

Scala case classes - Configuration

I Easy to update configurationI Easy to compare environments’ configuration

case class HttpClientConfig(

serviceName: String,

serviceBaseUrl: URL,

readTimeout: Int = 0,

connectionTimeout: Int,

proxy: Option[ProxyConfig] = None,

oauthKeys: Option[OAuthConfig] = None,

displayUrl: Option[String] = None

)

"httpConfig":{"serviceName":"recommendations",

"serviceBaseUrl":"http://nose.svc.ovi.com/rest/v1/recommendations/nearby/",

"readTimeout":4000,

"connectionTimeout":500,

"displayUrl":"http://nose.svc.ovi.com/rest/v1/recommendations/nearby/"

}

Page 9: Scala in Places API

The Scala Type System

I A great way to express assumptionsI Have them checked by the compiler through the whole codebaseI At every point in the code know what to expect or you will be reminded by

compiler.

Page 10: Scala in Places API

The Scala Type System - Options 1

I Avoid null references (see The Billion Dollar Mistake )

This will not compile!

case class UpstreamResponse ( unreliableField: Option[String] )

case class MyResponse ( reliableField: String )

def transform(upstream: UpstreamResponse) =

MyResponse(

reliableField = upstream.unreliableField //<-- incompatible type

)

Page 11: Scala in Places API

The Scala Type System - Options 2

But this will:

case class UpstreamResponse ( unreliableField: Option[String] )

case class MyResponse ( reliableField: String )

def transform(upstream: UpstreamResponse) =

MyResponse(

reliableField = upstream.unreliableField.getOrElse("n/a") // enforced by compiler

)

Page 12: Scala in Places API

The Scala Type System - Options 3

how we were learning e.g.: to transform string when it is defined

def transform(str: String): String

reliableField =

if (upstream.unreliableField.isDefined)

transform(upstream.unreliableField.get)

else "n/a"

reliableField =

upstream.unreliableField match {case Some(f) => transform(f)

case None => "n/a"

}

reliableField =

upstream.unreliableField.map(transform) | "n/a"

Page 13: Scala in Places API

The Scala Type System - Standard Types

I Immutable Map, List, Sets

val map: Map[String, String] = Map("a" -> "a", "b" -> "b")

val list: List[String] = "a" :: "b" :: Nil

I Tuples to express Pairs, Triplets etc.

val pair: Pair[String, Int] = ("a", 1)

val (a, b) = pair // defines a:String and b:Int

Page 14: Scala in Places API

The Scala Type System - error handlingI Types can help with exceptional cases

val parsed: Validation[NumberFormatException, Int] = someString.parseInt

val optional: Option[Int] = parsed.toOption

val withDefault: Int = optional.getOrElse(0) //if there was parsing problem 0

I Exception to Option

val sub: Option[String] =

allCatch.opt(line.substring(line.indexOf("{\"")))

I or Either

val parsed: Either[Throwable, Incident] =

allCatch.either(new Incident(parse(jsonString))

and handle it later

parsed match {case Right(j) => Some(j)

case Left(t) => {println("Parse error %s".format(t.getMessage))

None

}}

Page 15: Scala in Places API

The Scala Type System - functions

I we defer translation (Translated) and text rendering (FormattedText) toserialisation time using function composition so we don’t need to pass usercontext through the whole stack e.g.:

case class Place (

//(...)

attribution: Option[Translated[FormattedText]],

//(...)

)

case class Translated[A](translator: TransEnv => A) {def apply(env: TransEnv): A = translator(env)

def map[B](f: A => B): Translated[B] =

new Translated[B](env => f(apply(env)))

//(...)

}

Page 16: Scala in Places API

XML literals

XML can be part of the code and compiler is checking syntax of it:

def toKmlCoordinates(style: String): String =

(<Placemark>

<name>{"bbox " + this}</name><styleUrl>{style}</styleUrl><Polygon>

<outerBoundaryIs>

<LinearRing>

<coordinates>

{west + "," + north + ",0"}{east + "," + north + ",0"}{east + "," + south + ",0"}{west + "," + south + ",0"}{west + "," + north + ",0"}

</coordinates>

</LinearRing>

</outerBoundaryIs>

</Polygon>

</Placemark>).toString

Page 17: Scala in Places API

ScalacheckAutomatic testing of assumptions, using set of generated values.

e.g. the code below tests if

BBox.parse(a.toString) == a

is true for any a

val generator: Gen[BBox] =

for {s <- choose(-90.0, 90.0)

n <- choose(-90.0, 90.0)

if (n > s)

w <- choose(-180.0, 180.0)

e <- choose(-180.0, 180.0)

} yield new BBox(w, s, e, n)

property("parse . toString is identity") {check {

(a: BBox) =>

BBox.parse(a.toString) == a

}}

may yield this failing test message after several evaluations.

GeneratorDrivenPropertyCheckFailedException was thrown during property evaluation.

(BBoxTest.scala:32)

Falsified after 2 successful property evaluations.

Location: (BBoxTest.scala:32)

Occurred when passed generated values (

arg0 = 33.50207944036654,-63.96980425691198,59.04945970748918,8.222590173180834

)

Page 18: Scala in Places API

Scala (Scalding) in our analytics jobse.g. popular places job

class PopularPlacesJob(args: Args) extends AccessLogJob(args) {

implicit val mapMonoid = new MapMonoid[String, Long]()

val ord = implicitly[Ordering[(Long, String)]].reverse

readParseFilter()

.flatMap(’entry -> ’ppid) {entry:AccessLogEntry => entry.request.ppid

}.mapTo((’entry, ’ppid) -> (’ppid, ’time, ’app)) {

arg: (AccessLogEntry, String) => (arg._2, arg._1.dateTime, arg._1.appId.getOrElse("?"))

}.groupBy((’ppid, ’app)) {

_.min(’time -> ’minTime)

.max(’time -> ’maxTime)

.size(’num)

}.groupBy((’ppid)) {

_.min(’minTime)

.max(’maxTime)

.sum(’num)

.mapPlusMap(((’app, ’num) -> ’apps))

{ Map(_:(String, Long)) }{ _.toList

.map(a => (a._2, KnownClients.appName(a._1, "N/A")))

.sorted(ord)

.mkString(";")

}}.reverseSortAndLimit(’num)

.writeWithHeader(args("output"))

}

Page 19: Scala in Places API

Not so good in Scala

I Almost whole team needed to learn the new language - luckily we haveToralf ;)

I Tools are not as mature as those in Java (but they are getting better veryfast)

I Longer compilation, compiler has a lot more to do (e.g. type inference)

I Multiple inheritance (traits)

I Operators

I changes in every language release

Page 20: Scala in Places API

What we have learned, discovered?

I When done right - it’s possible to painlessly migrate code to newlanguage, developing new feautres in the same time

I How to use good typesystem and enjoy it

I Take advantage form whole great immutable world of painlessprogramming

I Use functions as first class citizens of language

Page 21: Scala in Places API

Plans

I Scala 2.10I Run Transformations in Futures, Validations etc.I Akka actors, Futures compositionI Kill last Java files?I Stop using Spring