680 likes | 831 Views
Effective Scala. HENRIK ENGSTRÖM SOFTWARE ENGINEER - TYPESAFE @h3nk3. $ whoami. Consultant since 1998 - mainly Java. Programming Scala since 2010. Been with Typesafe since 2011. Akka Honorary Team Member. Typesafe Console Tech Lead. Arsenal Supporter + Martial Artist. About Typesafe.
E N D
Effective Scala • HENRIK ENGSTRÖM • SOFTWARE ENGINEER - TYPESAFE • @h3nk3
$ whoami • Consultant since 1998 - mainly Java • Programming Scala since 2010 • Been with Typesafe since 2011 • Akka Honorary Team Member • Typesafe Console Tech Lead • Arsenal Supporter + Martial Artist
About Typesafe • The Typesafe Platform • Play, Akka, Scala, Scala IDE, Slick, SBT, etc. • Subscription • Training and Consulting
Agenda • Basic Stuff • Object Orientation in Scala • Implicits • Types • Collections • Pattern Matching • Functional Programming
// REPL = Read Eval Print Loop $scala_home/bin/scala WelcometoScalaversion2.10.0 scala>println("Hello, world!") Hello,world! scala> Use the REPL
// Put JAR files in scala_home/lib to get access $scala_home/bin/scala WelcometoScalaversion2.10.0 scala>importcom.x.y.z.MyClass scala>valinstance=newMyClass scala>instance.myMethod The REPL and JARs
REPL 2013 • IDE Worksheet • Scala IDE : awesome • IntelliJ : okay
// JAVA Stringresult=null; if(z<9)result="<9"elseresult=">=9"; System.out.println("Result: "+result); // SCALA println("Result: "+if(z<9)"<9"else">=9")) Expression vs Statement
// What type is variable quiz? varx=1 valquiz=while(x<10){ println("X is: "+x) x+=1 } Statement Pop Quiz • All expressions in Scala returns a type
// Don’t tell the computer how to fish importscala.collection.mutable.{HashSet=>MHS} deffindPeopleInCity(c:String,ppl:Seq[People]): Set[People]={ valfound=newMHS[People]() for(p<-ppl) for(a<-p.address) if(a.city==c)found.put(p) found } No Fishing Instructions Please!
deffindPeopleInCity(c:String,ppl:Seq[People]): Set[People]={ for{ p<-ppl.toSet[People] a<-p.address ifa.city==c }yieldp } // SQL LIKE SYNTAX; FROM, WHERE, AND, SELECT Instead - Just Order Fish
// 1) Mutable code leads to cloning // 2) Cloning leads to performance degradation // => Mutable code leads to worse performance classFootballer{ privatevarcars=Array[Car]() defsetCars(c:Array[Car]):Unit= cars=c.clone defgetCars:Array[Car]=cars.clone } Stay Immutable
// Safer code - use immutable collection classFootballer{ privatevarcars=Vector.empty[Car] defsetCars(c:Vector[Car])=cars=c defgetCars:Vector[Car]=cars } Stay Immutable
// Case classes make the class immutable scala>caseclassCar(brand:String) scala>caseclassFootballer(name:String,team: String,cars:Vector[Car]=Vector.empty) scala>varjw=newFootballer("Jack Wilshire", "Arsenal") Footballer(JackWilshire,Arsenal,Vector()) scala>jw=jw.copy(cars=Vector(Car("Porsche"))) Footballer(JackWilshire,Arsenal, Vector(Car(Porsche))) Case Classes ftw
Immutable Benefits • Simple equality • Simple hashcode • No need to lock • No defensive copying • Scala Case Classes • Automatic equality + hashcode (Murmur) • Lots of other goodies (e.g. copy)
// Sometimes local mutability makes sense importscala.collection.mutable.ArrayBuffer classMyClass{ deftheData:Seq[Int]={ valbuffer=newArrayBuffer[Int]() populateData(buffer) buffer.toSeq } } Local Mutability
// A classic game of null defauth(usr:String,pwd:String):Privileges= if(usr==null||usr.isEmpty|| pwd==null||pwd.isEmpty|| !validCredentials(usr,pwd)) withPrivileges(Anonymous) else privilegesFor(usr) Use Option
defauth(usr:Option[String],pwd:Option[String]): Privileges={ valprivileges:Option[Privileges]={ u<-usr p<-pwd if(!u.isEmpty&&!p.isEmpty) ifcanAuthenticate(u,p) }yieldprivilegesFor(u) privilegesgetOrElsewithPrivileges(Anonymous) } Use Option
traitSquareShape{ valwidth:Int valheight:Int valarea:Int=width*height } classRect(w:Int,h:Int)extendsSquaredShape{ overridevalwidth=w overridevalheight=h } scala>valr1=newRectangle(1,314) scala>r1.height res0:Int=314 scala>r1.area res1:Int=0 val for abstract members
Scala Initialization Order • From the Scala specification (section 5.1) • http://www.scala-lang.org/docu/files/ScalaReference.pdf • First, the superclass constructor is evaluated • Then, all base classes in the template’s linearization ... • Finally the statement sequence stats is evaluated
traitSquareShape{ defwidth:Int defheight:Int defarea:Int=width*height } classRect(w:Int,h:Int)extendsSquaredShape{ overridevalwidth=w overridevalheight=h } // or even better caseclassRect(width:Int,height:Int)extends SquaredShape def is much better
// Non-trivial return types should always be // annotated! defconvert(x:Int)=xmatch{ case1=>1.toChar case2=>true casez=>z.toByte } defconvert(x:Int):AnyVal=xmatch{ Annotate your APIs
Composition and Inheritance • Prefer composition over inheritance • easier to modify (e.g. DI) • Composition can use inheritance in Scala • leads to the famous cake pattern
traitUserRepoComponent{ defuserLocator:UserLocator defuserUpdater:UserUpdater traitUserLocator{ deffindAll:Vector[User] } traitUserUpdater{ defsave(user:User) } } Let’s bake a cake
traitJPAUserRepoComponentextendsUserRepoComponent{ defem:EntityManager defuserLocator=newJPAUserLocator(em) defuserUpdater=newJPAUserUpdater(em) classJPAUserLocator(em:EntityManager)extendsUserLocator{ deffindAll:Vector[User]=em.createQuery("from User", classOf[User]).getResultList.toVector } classJPAUserUpdater(em:EntityManager)extendsUserUpdater{ defsave(user:User)=em.persist(user) } } Baking in process
traitUserServiceComponent{ defuserService:UserService traitUserService{ deffindAll:Vector[User] defsave(user:User):Unit defcheckStatusOf(user:User):String } } Service Layer
traitDefaultUserServiceComponentextends UserServiceComponent{ this:UserRepositoryComponent=> defuserService=newDefaultUserService classDefaultUserServiceextendsUserService{ deffindAll=userLocator.findAll defsave(user:User)=userUpdater.save(user) defcheckStatus(user:User)= s"User $user seems okay to me" } } Service Layer Implementation
objectMyApplicationextendsApplication{ • valcompService= • newDefaultUserServiceComponent • withJPAUserRepositoryComponent{ • defem= • Persistence.createEntityManagerFactory( • "cakepattern").createEntityManager() • } • valservice=compService.userService • // use the service • } Use it
classMyTestextendsWordSpecwithMustMatcherswith Mockito{ traitMockedEntityManager{ defem=mock[EntityManager] } "service"must{ "return all users"in{ valcompService= newDefaultUserServiceComponent withJPAUserRepositoryComponent withMockedEntityManager // perform tests } } Test it
What is it good for? • Removes boilerplate code in a specific context • compile time safety • must be unambiguous though
traitAutoRepository{ deffind(regId:String)(implicitdbId:DBId): Option[Car] deffindAll(country:String)(implicitdbId:DBId): Seq[Car] } classDefaultAutoRepositoryextendsAutoRepository{ deffind(regId:String)(implicitdbId:DBId): Option[Car]={// ... } deffindAll(country:String)(implicitdbId:DBId): Seq[Car]={// ... } } Example
// Anti pattern classCarFinder{ valdbId=DbId("Dealer1") valrepo=newDefaultAutoRepository defgetCar(regId:String):Option[Car]= repo.find(regId)(dbId) deflistCars(country:String):Seq[Car]= repo.findAll(country)(dbId) } Example continued
// Use implicits => much cleaner code classCarFinder{ implicitvaldbId=DbId("Dealer1") valrepo=newDefaultAutoRepository defgetCar(regId:String):Option[Car]= repo.find(regId) deflistCars(country:String):Seq[Car]= repo.findAll(country) } Example continued
Compiler workout • Implicit scope • Lexical : current scope, explicit imports, wildcard imports • Companions of parts of the type : companion of types, companion of types of arguments, outer objects of nested types • Can be expensive in compile time - use with care
traitLogger{deflog(msg:String)} objectLogger{ implicitobjectDefaultLoggerextendsLogger{ deflog(msg:String)=println("DL> "+msg) } deflog(msg:String)(implicitlogger:Logger)={ logger.log(msg) } } Implicit Values
scala>Logger.log("a small test") DL> a small test scala>classMyLoggerextendsLogger{ deflog(msg:String)=println("ML:>> "+msg) } scala>implicitdefmyLogger=newMyLogger scala>Logger.log("another test") ML:>> another test Implicit Values
Implicits wisdom? deech @deech Debugging #scala implicits is like trying to find the farter in a crowed room
// "describes generic interfaces using type // parameters such that the implementations can // be created for any type" traitEncodable[T]{ deffrom(t:T):String defto(s:String):T } objectEncodable{ implicitobjectIntEncoderextendsEncodable[Int]{ deffrom(i:Int):String="int"+i defto(s:String):Int= s.substring(s.indexOf("int")+3,s.length).toInt } } a.k.a Type Classes
classMyHandler{ defconvert[T](t:T)(implicitenc:Encodable[T]): String=enc.from(t) defconvert[T](s:String)(implicitenc: Encodable[T]):T=enc.to(s) } scala>valmyHandler=newMyHandler scala>myHandler.convert(12345) res0:String=int12345 scala>myHandler.convert(res0) res1:Int=12345 Example Usage
scala>myHandler.convert(12345L) <console>:15: error: could not find implicit value for parameter encoder: Encodable[Long] scala>implicitobjectLongEncextends Encodable[Long]{ deffrom(l:Long):String="long"+l defto(s:String):Long= s.substring(s.indexOf("long")+4, s.length).toLong} scala>myHandler.convert(12345L) Example continued
// It is absolutely *awesome* scala>valseq=Seq() scala>seq. ++ ++: +: /: /:\ :+ :\ addString aggregate andThen apply applyOrElse asInstanceOf canEqual collect collectFirst combinations companion compose contains containsSlice copyToArray copyToBuffer corresponds count diff distinct drop dropRight dropWhile endsWith exists filter filterNot find flatMap flatten fold foldLeft foldRight forall foreach genericBuilder groupBy grouped hasDefiniteSize head headOption indexOf indexOfSlice indexWhere indices init inits intersect isDefinedAt isEmpty isInstanceOf isTraversableAgain iterator last lastIndexOf lastIndexOfSlice lastIndexWhere lastOption length lengthCompare lift map max maxBy min minBy mkString nonEmpty orElse padTo par partition patch permutations prefixLength product reduce reduceLeft reduceLeftOption reduceOption reduceRight reduceRightOption repr reverse reverseIterator reverseMap runWith sameElements scan scanLeft scanRight segmentLength seq size slice sliding sortBy sortWith sorted span splitAt startsWith stringPrefix sum tail tails take takeRight takeWhile to toArray toBuffer toIndexedSeq toIterable toIterator toList toMap toSeq toSet toStream toString toTraversable toVector transpose union unzip unzip3 updated view withFilter zip zipAll zipWithIndex Explore and learn the API
Message to Java Developers • Use Vector not List • it is faster • it is more memory efficient