Extractors & Implicit conversions

Preview:

Citation preview

Abdhesh Kumarabdhesh@knoldus.com

Extractors & Implicit conversions

Extractors

update method

Implicit conversions, parameters and implicit context

ExtractorPattern Matching???

object ListExtractor { val xs = 3 :: 6 :: 12 :: Nil xs match { case List(a, b) => a * b case List(a, b, c) => a + b + c case _ => 0 }}

object knolx { val head :: tail = List(1, 2, 3)}

Extractor

If you have some student data, each is "B123456, Bob, New Delhi" format, you want to Obtained a student number, name and place of birth information

object Student { def separate(s: String) = { val parts = s.split(",") if (parts.length == 3) Some((parts(0), parts(1), parts(2)) else None }}

object StudentApp extends App { val student = Student.separate("B123456, Bob, New Delhi") student match { case Some((number, name, addr)) => println(s"Student Information:::name=$name, number=$number and address=$addr") case None => println("Wrong data found") }}

val students = List("B123456,Ankit,New Delhi", "B123455,Anand,New Delhi","B123454,Satendra,New Delhi")

This is possible??val Student(number, name, addr) = "B123456, Bob, New Delhi"

Extractor (unapply)

case class Student(number: String, name: String, address: String) val students = List("B123456,Ankit,New Delhi", "B123455,Anand,New Delhi","B123454,Satendra,New Delhi")

val info: List[Student] = ???

Extractor (unapply)

def unapply(object: S): Option[T]

object Student { def unapply(str: String): Option[(String, String, String)] = { val parts = str.split(",") if (parts.length == 3) Some(parts(0), parts(1), parts(2)) else None }}

Extractor (unapply)

object StudentApp extends App { val Student(number, name, addr) = "B123456, Bob, New Delhi" println(s"Student information=${(number, name, address)}") }

Extractor (unapply)

object StudentApp extends App { val students = List( "B123456,Ankit,New Delhi", "B123455,Anand,New Delhi", "B123454,Satendra,New Delhi")

val result: List[Student] = students collect { case Student(number, name, address) => Student(number, name, address) } println(result)}

Extractor (unapply)

case class Student(number: String, name: String, address: String)

object Student { def unapply(str: String): Option[(String, String, String)] = { val parts = str.split(",") if (parts.length == 3) Some(parts(0), parts(1), parts(2)) else None }}

object Utility {

def extractInformation(data: List[String]): List[Student] = { def extractInfo(extractedInfo: List[Student], result: List[String]): List[Student] = { result match { case Student(number, name, address) :: tail => extractInfo(extractedInfo :+ Student(number, name, address), tail) case Nil => extractedInfo case _ :: tail => extractInfo(extractedInfo, tail) } } extractInfo(List[Student](), data) }}

Safe Extractor (unapply)

object EMail { // The extraction method (mandatory) def unapply(str: String): Option[(String, String)] = { val parts = str split "@" if (parts.length == 2) Some(parts(0), parts(1)) else None }}

object SafeExtractor extends App { val any: Any = "abdhehs@knoldus.com" any match { case EMail(user, domain) => println(s"Matched:::${(user, domain)}") case _ => println("Not Mathed") }}

Extractor (unapply)

object GivenNames { def unapply(name: String): Option[(String, String)] = { val names = name.trim.split(",") if (names.length >= 2) Some((names(1), names(0))) else None }}

object ExtractNames extends App { def greetWithFirstName(name: String) = name match { case GivenNames(firstName, _) => "Good evening, " + firstName + "!" case _ => "Welcome! Please make sure to fill in your name!" }

val information = "Kumar,Abdhesh,New Delhi" val result = greetWithFirstName(information) println(result)}

object UserName { def unapplySeq(email: String): Option[Seq[String]] = { val parts = email split "," if (parts.nonEmpty) Some(parts) else None }}

object UserNameApp extends App { val name = "Kumar,Abdhesh,New Delhi" name match { case UserName(lastName, firstName, _*) => println(s"First Name=$firstName and Last Name=$lastName") case _ => println("Not Matched") }

val UserName(lastName, firstName, others @ _*) = name}

Extract sequences (unapplySeq)

object UserName { def unapplySeq(email: String): Option[Seq[String]] = { val parts = email split "," if (parts.nonEmpty) Some(parts) else None }}

object UserNameApp extends App { val name = "Kumar,Abdhesh,New Delhi" name match { case UserName(lastName, firstName, others @ _*) => println(s"First Name=$firstName and Last Name=$lastName") case _ => println("Not Matched") }}

Extract sequences (unapplySeq)..

Wildcard operators

If you only care about the first variable you can use _* to reference the rest of the sequence that you don't care about

using @ _* we can get the rest of the sequence assigned to others

Combining fixed and variable parameter extraction

Sometimes, you have certain fixed values to be extracted that you know about at compile time, plus an additional optional sequence of values.

def unapplySeq(object: S): Option[(T1, .., Tn-1, Seq[T])]

*unapplySeq can also return an Option of a TupleN , where the last element of the tuple must be the

Combining fixed and variable parameter extraction

object Names { def unapplySeq(name: String): Option[(String, String, Seq[String])] = { val names = name.trim.split(" ") if (names.size < 2) None else Some((names.last, names.head, names.drop(1).dropRight(1))) }}

object MixedExtractor { def greet(fullName: String) = fullName match { case Names(lastName, firstName, _*) => "Good morning, " + firstName + " " + lastName + "!" Case _ => "Welcome! Please make sure to fill in your name!" }}

A Boolean Extractor

object StudentBool { def unapply(student: String): Boolean = student.contains("Allahabad")}

object BooleanExtractorApp extends App { val name = "B123456,Ankit,Allahabad" name match { case studentI @ StudentBool() => println(studentI) case _ => println("Not Found") }}

A Boolean Extractor

object StudentBool { def unapply(student: String): Boolean = student.contains("Allahabad")}

object BooleanExtractorApp extends App { val students = List("B123455,Anand,New Delhi","B123454,Satendra,New Delhi","B123456,Ankit,Allahabad")

students collect { case std @ StudentBool() => std }}

Instances of a class with unapply method

class Splitter(sep: Char) {def unapply(v: String): Option[(String, String)] = (v indexOf sep) match { case x if (x > 0) => Some((v take x, v drop x + 1)) case _ => None }}

object SimpleExtractor extends App { val SplitOnComma = new Splitter(',') "1,2" match { case SplitOnComma(one, two) => println(one, two) } //All extractors can also be used in assignments val SplitOnComma(one, two) = "1,2"}

Every Regex object in Scala has an extractor

object RegexExtractor extends App {

val Decimal = new Regex("""(-)?(\d+)(\.\d*)?""") val input = "for -1.0 to 99 by 3"

for (s <- Decimal findAllIn input) println _ val dec = Decimal findFirstIn input val Decimal(sign, integerpart, decimalpart) = "-1.23" println((sign, integerpart, decimalpart)) val Decimal(signI, integerpartI, decimalpartI) = "32323"

// Regex to split a date in the format Y/M/D. val regex = "(\\d+)/(\\d+)/(\\d+)".r val regex(year, month, day) = "2010/1/13"}

Extractor Rules

unapply () method called extractor unapply can’t be overloaded (because it always takes the the same argument).

Implicts

Where Implicits are Tried

Implicits are used in three places in Scala

○ Conversions to an expected type

■ For objects passed in to a function that expects a different type

Where Implicits are Tried..

○ Conversions of the receiver of a selection

■ For when methods are called on an object that doesn't have those methods defined, but an implicit conversion does

(e.g. -> method for maps)

Where Implicits are Tried...

○ Implicit Parameters

■ Provide more information to a called method about what the caller wants

■ Especially useful with generic functions

Rules for Implicit

Marking Rule: Only definitions marked implicit are available

The implicit keyword is used to mark which declarations the

compiler may use as implicits.

implicit def intToString(x: Int) = x.toString def dbl2Str(d: Double): String = d.toString // ignored

Rules for Implicit

Scope Rule: An inserted implicit conversion must be in

scope as a single identifier, or be associated with the source

or target type of the conversion.

The Scala compiler will only consider implicit conversions that are in scope

The compiler will also look for implicit definitions in the companion object of

the source or expected target types of the conversion

Rules for Implicit

Non-Ambiguity Rule: An implicit conversion is only inserted if

there is no other possible conversion to insert.

One-at-a-time Rule: Only one implicit is tried.

The compiler will never rewrite x + y to convert1(convert2(x)) + y.

Rules for Implicit

Explicits-First Rule: Whenever code type checks as it is written,

no implicits are attempted

Naming Conventions for Implicit

Can have arbitrary names

The compiler doesn't care

Naming should be chosen for two considerations:

Explicit usage of the conversion

Explicit importing into scope for the conversion

Implicits Methods

The idea is to be able to extend functionality of an existing class with new

methods in a type safe manner.

Implicits Methods

class MyInteger(val i: Int) { def myNewMethod = println("hello from myNewMethod") def inc = new MyInteger(i + 1)}

object MyIntegerConvertor { implicit def int2MyInt(i: Int) = new MyInteger(i) implicit def myInt2Int(mI: MyInteger) = mI.i}

object ImplicitMethods extends App { import MyIntegerConvertor._

val result = 1.myNewMethod val inc = 2.inc def takesInt(i: Int) = println(i) takesInt(inc) takesInt(5.inc)}

Implicits Parameters

It is similar to default parameters, but it has a different

mechanism for finding the “default” value.

The implicit parameter is a parameter to method or constructor that is

marked as implicit.

This means if a parameter value is not mentioned then the

compiler will search for an “implicit” value defined within a scope.

Implicits Parameters

object ImplicitParameters { def p(implicit i: Int) = print(i) def sayThings(implicit args: List[Any]) = args.foreach(println(_))}

object ImplicitParametersApp extends App { import ImplicitParameters._ implicit val nothingNiceToSay: List[Any] = Nil sayThings implicit val v = 2}

Implicits Parameters

Matching implicit arguments Implicits are totally typesafe, and are selected based on the static type of

the

arguments. Here are some examples to show how things work.

def speakImplicitly(implicit greeting: String) = println(greeting)

implicit val aUnit = ()

speakImplicitly //no implicit argument matching parameter type String was found.

Implicits Parameters

Implicit arguments of subtypes

def sayThings(implicit args: List[Any]) = args.foreach(println(_)) implicit val nothingNiceToSay: List[Any] = Nil

sayThings

implicit val hello: List[String] = List("Hello world");

sayThings

Implicits Parameters

/**One important restriction is that there can only be a single implicit keyword per method. It must be at the start of a parameter list (which also makes all values of that parameter list be implicit).*/

def pp(a: Int)(implicit i: Int) = println(a, i)

//both i and b are implicit def pp(a: Int)(implicit i: Int, b: Long) = println(a, i, b)

def pp(implicit i: Int, b: Long) = println(i, b)

def pp(implicit i: Int, b: Long*) = println(i, b)

def pp(b: Long*)(implicit i: Int) = println(i, b)

/When you have a implicit keyword in the first of parameter list, all the parameters in this list are implicit!

Implicit Classes

Must take exactly one parameter (Though can have a second, implicit parameter list)

Must be defined where a method could be defined (not at top level)

Generated method will have same name as the class, so can be imported together.

Implicit Classes

object Helpers { implicit class IntWithTimes(x: Int) { def times[A](f: => A): Unit = { def loop(current: Int): Unit = if (current > 0) { f loop(current - 1) } loop(x) } }}

object ImpliciClasses { import Helpers._

5 times println("HI")}

Implicit Classes

Implicit classes have the following restrictions:

1. They must be defined inside of another trait / class / object .

2.They may only take one non­implicit argument in their constructor.

3. There may not be any method, member or object in scope with the same name as the

implicit class.

Sort User Defined Objects

case class Employee(id: Int, firstName: String, lastName: String)

object Employee { implicit def orderingByName[A <: Employee]: Ordering[A] = Ordering.by(e => e.firstName)

val orderingById: Ordering[Employee] = Ordering.by(e => e.id)

}

Sort User Defined Objects

object ImplicitBox extends App { val employees = List( Employee(1, "A", "AA"), Employee(4, "D", "AA"), Employee(5, "E", "EE"), Employee(2, "B", "BB"), Employee(3, "C", "CC"))

val sortByName = employees.sorted println(s"Sort Employee by name=${sortByName}")

val sortById = employees.sorted(Employee.orderingById) println(s"Sort Employee by Id=${sortById}")}

How can I chain/nest implicit conversions?

Scala does not allow two such implicit conversions taking place, however, so one cannot got from A to C using an implicit A to B and another implicit B to C .

Is there a way around this restriction?

Scala has a restriction on automatic conversions to add a method, which is that it won’t apply more than one conversion in trying to find methods.

How can I chain/nest implicit conversions?

object T1 { implicit def toA(n: Int): A = new A(n) implicit def aToB(a: A): B = new B(a.n, a.n) implicit def bToC(b: B): C = new C(b.m, b.n, b.m + b.n) // won't work //println(5.total) //println(new A(5).total) // works println(new B(5, 5).total) println(new C(5, 5, 10).total)}

How can I chain/nest implicit conversions?

// def m[A <% B](m: A) is the same thing as// def m[A](m: A)(implicit ev: A => B)

object T2 extends App { implicit def toA(n: Int): A = new A(n) implicit def aToB[A1 <% A](a: A1): B = new B(a.n, a.n) implicit def bToC[B1 <% B](b: B1): C = new C(b.m, b.n, b.m + b.n)

// works println(5.total) println(new A(5).total) println(new B(5, 5).total) println(new C(5, 5, 10).total)}

update method

a(x) = y, the compiler interprets this to as a.update(x, y)

class PhonebookExpend {private val numbers = scala.collection.mutable.Map[String, (Int, Int)]() def apply(name: String) = numbers(name) def update(name: String, number: (Int, Int)) = numbers(name) = number}

object EchoUpdate extends App { val e = new Echo() e("hello", "hi") = "knoldus" //e("hello") = ("salut", "bonjour")

val book2 = new PhonebookExpend()

book2("jesse") = (123, 4567890) val (areaCode, local) = book2("jesse")}

References

http://daily­scala.blogspot.in/

Programming in Scala Book

Thanks.

Recommended