Month: August 2016

JSON

Case classes are Scala’s preferred way to define complex data. Below is one way to represent JSON data

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

abstractclassJSON

caseclassJSeq(elems:List[JSON])extendsJSON

caseclassJObj(bindings:Map[String,JSON])extendsJSON

caseclassJNum(num:Double)extendsJSON

caseclassJStr(str:String)extendsJSON

caseclassJBool(b:Boolean)extendsJSON

caseobjectJNullextendsJSON

objectJSONAppextendsApp{

def show(json:JSON):String=jsonmatch{

caseJSeq(elems)=>"["+(elems map show mkString",")+"]"

caseJObj(bindings)=>

val assocs=bindingsmap{

case(key,value)=>"\""+key+"\": "+show(value)

}

"{"+(assocs mkString",")+"}"

caseJNum(num)=>num.toString

caseJStr(str)=>"\""+str+"\""

caseJBool(b)=>b.toString

caseJNull=>"null"

}

val data=JObj(Map(

"firstName"->JStr("John"),

"lastName"->JStr("Smith"),

"address"->JObj(Map(

"streetAddress"->JStr("21 2nd Street"),

"state"->JStr("NY"),

"postalCode"->JNum(10021)

)),

"phoneNumbers"->JSeq(List(

JObj(Map(

"type"->JStr("home"),"number"->JStr("212 555-1234")

)),

JObj(Map(

"type"->JStr("fax"),"number"->JStr("646 555-4567")

))

))

))

println(show(data))

}

Left hand-side of a For-expression generator can also be a pattern as shown below which lists all first-name and last-name of phone#s that start with 212:

1

2

3

4

5

6

7

8

val data:List[JSON]=...

for{

JObj(bindings)<-data

JSeq(phones)=bindings(”phoneNumbers”)

JObj(phone)<-phones

JStr(digits)=phone(”number”)

ifdigits startsWith”212”

}yield(bindings(”firstName”),bindings(”lastName”))

Here JObj(bindings) acts as implicit filter

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

val books:List[Book]=List(

Book(title="Book 1"

authors=List("Author-1","Author-2")),

Book(title="Book 2"

authors=List(”Bird,Richard”,”Wadler,Phil”)),

....)

{for{

b1<-books

b2<-books

ifb1.title<b2.title

a1<-b1.authors

a2<-b2.authors

ifa1==a2

}yield a1

}.distinct

distinct is needed to remove duplicate authors who are in results list more than twice

Partial Functions

Partial Function, as opposed to Total functions, provides an answer only for a subset of possible data, and defines the data it can handle by isDefined as follows:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

val divide=newPartialFunction[Int,Int]{

def apply(x:Int)=42/x

def isDefinedAt(x:Int)=x!=0

}

scala>divide(0)

java.lang.ArithmeticException:/by zero

scala>divide.isDefinedAt(1)

res0:Boolean=true

scala>if(divide.isDefinedAt(1))divide(1)

res1:AnyVal=42

scala>divide.isDefinedAt(0)

res2:Boolean=false

More common way of writing Partial Functions is using "case" statement which gives a default implementation of isDefined:

1

2

3

4

5

6

7

8

9

10

11

12

val divide2:PartialFunction[Int,Int]={

cased:Intifd!=0=>42/d

}

scala>divide2(0)

scala.MatchError:0(of classjava.lang.Integer)

scala>divide2.isDefinedAt(0)

res0:Boolean=false

scala>divide2.isDefinedAt(1)

res1:Boolean=true

Note that our exception changed to MatchError instead of ArithmeticException when using "case".

Partial-Functions are well handled by "collect" as shown below:

1

2

3

4

5

scala>List(41,"cat")map{casei:Int⇒i+1}

scala.MatchError:cat(of classjava.lang.String)

scala>List(41,"cat")collect{casei:Int⇒i+1}

res1:List[Int]=List(42)

Magic here is that collect expects a PartialFunction and invokes the isDefined method. If we define partial-function inline, the compiler knows that it’s a partial function and you avoid explicit PartialFunction trait.

Seq,Set and Map are also Partial-functions

1

2

3

4

5

6

7

8

9

10

11

val pets=List("cat","dog","frog")

scala>pets(0)

res13:java.lang.String=cat

scala>pets(3)

java.lang.IndexOutOfBoundsException:3

scala>pets.isDefinedAt(0)

res14:Boolean=true

scala>pets.isDefinedAt(3)

res15:Boolean=false

scala>Seq(1,2,42)collect pets//safely collect values of indexes

res16:Seq[java.lang.String]=List(dog,frog)

Checking for isDefined can be painful and luckily Scala supports lift method that converts partial-function to total-function which returns an Option

We can chain together partial-functions using orElse or andThen which are defined in PartialFunction trait just like lift.

1

2

3

4

5

6

// converts 1 to "one", etc., up to 5

val convert1to5=newPartialFunction[Int,String]{....}

// converts 6 to "six", etc., up to 10

val convert6to10=newPartialFunction[Int,String]{....}

scala>val handle1to10=convert1to5 orElse convert6to10

handle1to10:PartialFunction[Int,String]=

withFilter

Scala provides another variation of filter called withFilter which doesn't create a new collection like filter does. It acts like a view that filters the result to be passed on to subsequent calls to map/flatMap etc.

Translation of for is not limited to lists or sequences or even collections.
It is based solely on the presence of the methods map, flatMap and withFilter. This lets us use for syntax for our own types as well – you must only define map, flatMap and withFilter for these types.
There are many types for which this is useful: arrays, iterators, databases, XML data, optional values, parsers, etc.

As long as client interface to the database defines the methods map, flatMap and withFilter we can use for syntax database querying.
This is the basis of Scala database connection frameworks ScalaQuery and Slick.
Similar ideas underly Microsoft’s LINQ.

Streams

Streams are similar to Lists except for their Tails are evaluated on demand.

Streams are defined from a constant Stream.empty and a constructor Stream.cons.

1

val xs=Stream.cons(1,Stream.cons(2,Stream.empty))

They can also be defined like the other collections by using the
object Stream as a factory Stream(1, 2, 3)

The toStream method on a collection will turn the collection into a stream:(1 to 1000).toStream > res0: Stream[Int] = Stream(1, ?)

Stream supports almost all methods of List except for :: which always produces a List. #:: should be used instead to produce a Stream which can be used in Expressions as well as Patterns.

To find the second prime number between 1000 and 10000:((1000 to 10000).toStream filter isPrime)(1)

x #:: xs == Stream.cons(x, xs)

Even the implementation of Streams are very close to Lists with only major difference being the use of "Call-by-name" to declare second param of cons ro filter ops as follows which causes lazy-evaluation.