Scala flatmap and Parallel programming

We usually use flatmap and map in scala. Of course, if we use future we chaining by using flatmap or map… etc. But is this run parallel? In fact (as you know) it’s not parallel. If you chaining some monads, it runs sequentially.

Future chaining is not parallel

Let’s see some example. Guess that, there is some db dependent function like beneath

case class UserInfo(userId: Int, nickname: String, emailAddress: String, account: Option[Long])
def getNicknameById(userId: Int): Future[String] = Future {
Thread.sleep(2000)
"ktz"
}
def getEmailAddressByNickname(nickname: String): Future[String] = Future {
Thread.sleep(2000)
"helloworld@example.com"
}
def getAccountInfoByNickname(nickname: String): Future[Option[Long]] = Future {
Thread.sleep(3000)
None
}
def printInterval(userInfo: UserInfo, start: Instant, end: Instant): String =
s"$userInfo take ${(end.toEpochMilli - start.toEpochMilli) / 1000} seconds"
val userId: Int = 31337
view raw simulation.sc hosted with ❤ by GitHub

Each takes 2 seconds, 2 seconds, 3 seconds. And we want to make userInfo by calling functions. You can call like this.

/**
* Just use for comprehension
*/
val start1: Instant = Instant.now
val fUserInfo1 = for {
nickname <- getNicknameById(userId)
emailAddr <- getEmailAddressByNickname(nickname)
accountInfo <- getAccountInfoByNickname(nickname)
} yield UserInfo(userId, nickname, emailAddr, accountInfo)
val userInfo1 = Await.result(fUserInfo1, 10 second)
val end1: Instant = Instant.now
println(printInterval(userInfo1, start1, end1)) // UserInfo(31337,ktz,helloworld@example.com,None) take 7 seconds

As you can see, It takes 7 seconds (2 sec + 2 sec + 3 sec). It is not parallel.

How to be parallel?

by function

There easy 2 ways to be parallel. Solve this by function. You can make like this.

/**
* Calling first and use next
*/
def getEmailAndAccountTuple(fNickname: Future[String]): Future[(String, Option[Long])] =
fNickname.flatMap(nickname => {
val fEmailAddr = getEmailAddressByNickname(nickname)
val fAccountInfo = getAccountInfoByNickname(nickname)
fEmailAddr.flatMap(email => fAccountInfo.map(account => (email, account)))
})
val start2: Instant = Instant.now
val fNickname: Future[String] = getNicknameById(userId)
val fUserInfo2 = for {
nickname <- fNickname
(email, accountInfo) <- getEmailAndAccountTuple(fNickname)
} yield UserInfo(userId, nickname, email, accountInfo)
val userInfo2 = Await.result(fUserInfo2, 10 second)
val end2: Instant = Instant.now
println(printInterval(userInfo2, start2, end2)) // UserInfo(31337,ktz,helloworld@example.com,None) take 5 seconds

It takes 5 seconds(2 seconds + max(2 seconds, 3 seconds)). It’s because, before chaining, Future already started. And after getting an email address, 2 seconds, we only need to wait for 1 second to get accountInfo because it runs parallel.

by using the Cartesian product and applicative

But it has to make function. It’s annoying. We can simply make this by using cats.

/**
* Use cats applicative
*/
import cats.syntax.all._
import cats.instances.future._
val start3: Instant = Instant.now
val fUserInfo3 = for {
nickname <- getNicknameById(userId)
(email, account) <- (getEmailAddressByNickname(nickname) |@| getAccountInfoByNickname(nickname)).tupled
} yield UserInfo(userId, nickname, email, account)
val userInfo3: UserInfo = Await.result(fUserInfo3, 10 second)
val end3: Instant = Instant.now
println(printInterval(userInfo3, start3, end3)) // UserInfo(31337,ktz,helloworld@example.com,None) take 5 seconds
view raw UserApplicative.sc hosted with ❤ by GitHub

It also takes 5 seconds, But it is simpler than by using a function.

import java.time.Instant
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.{Await, Future}
import scala.concurrent.duration._
case class UserInfo(userId: Int, nickname: String, emailAddress: String, account: Option[Long])
def getNicknameById(userId: Int): Future[String] = Future {
Thread.sleep(2000)
"ktz"
}
def getEmailAddressByNickname(nickname: String): Future[String] = Future {
Thread.sleep(2000)
"helloworld@example.com"
}
def getAccountInfoByNickname(nickname: String): Future[Option[Long]] = Future {
Thread.sleep(3000)
None
}
def printInterval(userInfo: UserInfo, start: Instant, end: Instant): String =
s"$userInfo take ${(end.toEpochMilli - start.toEpochMilli) / 1000} seconds"
val userId: Int = 31337
/**
* Just use for comprehension
*/
val start1: Instant = Instant.now
val fUserInfo1 = for {
nickname <- getNicknameById(userId)
emailAddr <- getEmailAddressByNickname(nickname)
accountInfo <- getAccountInfoByNickname(nickname)
} yield UserInfo(userId, nickname, emailAddr, accountInfo)
val userInfo1 = Await.result(fUserInfo1, 10 second)
val end1: Instant = Instant.now
println(printInterval(userInfo1, start1, end1)) // UserInfo(31337,ktz,helloworld@example.com,None) take 7 seconds
/**
* Calling first and use next
*/
def getEmailAndAccountTuple(fNickname: Future[String]): Future[(String, Option[Long])] =
fNickname.flatMap(nickname => {
val fEmailAddr = getEmailAddressByNickname(nickname)
val fAccountInfo = getAccountInfoByNickname(nickname)
fEmailAddr.flatMap(email => fAccountInfo.map(account => (email, account)))
})
val start2: Instant = Instant.now
val fNickname: Future[String] = getNicknameById(userId)
val fUserInfo2 = for {
nickname <- fNickname
(email, accountInfo) <- getEmailAndAccountTuple(fNickname)
} yield UserInfo(userId, nickname, email, accountInfo)
val userInfo2 = Await.result(fUserInfo2, 10 second)
val end2: Instant = Instant.now
println(printInterval(userInfo2, start2, end2)) // UserInfo(31337,ktz,helloworld@example.com,None) take 5 seconds
/**
* Use cats applicative
*/
import cats.syntax.all._
import cats.instances.future._
val start3: Instant = Instant.now
val fUserInfo3 = for {
nickname <- getNicknameById(userId)
(email, account) <- (getEmailAddressByNickname(nickname) |@| getAccountInfoByNickname(nickname)).tupled
} yield UserInfo(userId, nickname, email, account)
val userInfo3: UserInfo = Await.result(fUserInfo3, 10 second)
val end3: Instant = Instant.now
println(printInterval(userInfo3, start3, end3)) // UserInfo(31337,ktz,helloworld@example.com,None) take 5 seconds
view raw ChaingFuture.sc hosted with ❤ by GitHub

Implicit in Scala

In Scala, Implicit is very important. But it is very ambiguous and hard to understand. And it is learning curve to enter Scala world. This post is basic about Scala Implicit.

Implicit is operated by the compiler. If there is some error, for example, Instance type is not compatible or need implicit parameter Compiler is looking for Implicit conversion or Implicit Instance. And this post is about making implicit Conversion or Instance.

Implicit Conversion

implicit def

We can convert some instance to other instance automatically by using implicit. First, we can use ‘Implicit Conversion’. Let’s say that, there are some instances like that.

trait Printable {
def print: String
}
case class PrintableClass(value: String) extends Printable {
override def print: String = s"value: ${value.toString}"
}
case class ValueCaseClass(value: String)
view raw Instance.sc hosted with ❤ by GitHub

If we want to convert ‘ValueCaseClass’ to ‘PrintableClass’, you can use by using implicit def like this.

implicit def convertValueCaseClassToPrintable(valueCaseClass: ValueCaseClass): PrintableClass =
PrintableClass(valueCaseClass.value)
val printableClass: PrintableClass = ValueCaseClass("Implicit Def") // by using convertValueCaseClassToPrintable
println(printableClass.print)
view raw ImplicitDef.sc hosted with ❤ by GitHub

In this code,


val printableClass1: PrintableClass = ValueCaseClass("Implicit Def")

printableClass1’s type is PrintableClass but we set ValueCaseClass. It is compile error. But, Compile is looking for implicit conversion and finally find implicit definition ‘convertValueCaseClassToPrintable’ and get PrintableClass.

implicit class

You can also convert by using implicit class.

implicit class ConvertableValueCaseClass(valueCaseClass: ValueCaseClass) {
def toPrintable: PrintableClass = PrintableClass(valueCaseClass.value)
}
val printableClass2: PrintableClass = ValueCaseClass("Implicit class").toPrintable // by using implicit class
println(printableClass2.print)
view raw ImplicitClass.sc hosted with ❤ by GitHub

In this code,


val printableClass2: PrintableClass = ValueCaseClass("Implicit class").toPrintable

ValueCaseClass don’t has toPrintable. Compiler looking for implicit class and find ‘ConvertableValueCaseClass’ and there is ‘toPrintable’.

Implicit Instance

In Scala, def can get implicit parameter.

def print(implicit printableClass: PrintableClass): Unit = {
println(printableClass.print)
}
implicit val printableClass = PrintableClass("Implicit Val")
print
view raw ImplicitVal.sc hosted with ❤ by GitHub

In code above, ‘print’ need ‘printableClass’ parameter. Compiler automatically pass implicit instance ‘printableClass’.

Implicit Order

Above, there we can get the implicit instance. And I think it is very useful. We can get Implicit Instance in many ways. Let see an example.

object ImplicitOrder extends App{
implicit val implicitIntInstance1: Instance[String] = Instance("Implicit Instance in Same Conjure")
def getString(implicit implicitInstance: Instance[String]): String = implicitInstance.value
import Imported.implicitIntInstance1
println(getString)
}
case class Instance[T](value: T)
object Instance {
implicit val implicitIntInstance1: Instance[String] = Instance("Implicit Instance in Companion Object")
}
object Imported {
implicit val implicitIntInstance1: Instance[String] = Instance("Implicit Instance in imported")
}
view raw ImplicitOrder.scala hosted with ❤ by GitHub

Can you guess which string is printed in the console? The answer is ‘Implicit Instance in Same Conjure’ Implicit order is

  1. In same conjure
  2. Imported instance
  3. companion object
  4. default imported

So, if we delete

def getString(implicit implicitInstance: Instance[String]): String = implicitInstance.value

, “Implicit Instance in imported” will be printed. And if we delete

import Imported.implicitIntInstance1

, “Implicit Instance in Companion Object” will be printed

Default implicit instance by companion object.

Let’s pay attention to the companion object implicit instance. We can make default instance by implicit instance in companion object.

case class Hello(value: String)
trait Printer[T] {
def print(value: T): String
}
object Printer {
implicit val IntPrinter: Printer[Int] = new Printer[Int] {
override def print(value: Int): String = s"Type: Int - $value"
}
implicit val PersonPrinter: Printer[Hello] = new Printer[Hello] {
override def print(value: Hello): String = s"Type: Hello - $value"
}
}
def print[T](value: T)(implicit printer: Printer[T]): String = printer.print(value)
val int: Int = 1
val hello: Hello = Hello("ktz")
print(int) // Type: Int - 1
print(hello) // Type: Hello - Hello(ktz)
object InjectedObject {
implicit val injectedIntPrinter: Printer[Int] = new Printer[Int] {
override def print(value: Int): String = s"Injected - Type: Int - $value"
}
}
import InjectedObject.injectedIntPrinter
print(int) // Injected - Type: Int - 1

Above code


print(int) // Type: Int - 1
print(hello) // Type: Hello - Hello(ktz)

Function ‘print’ is passed default implicit instance in companion object


implicit val IntPrinter: Printer[Int] = new Printer[Int] {
  override def print(value: Int): String = s"Type: Int - $value"
}

implicit val PersonPrinter: Printer[Hello] = new Printer[Hello] {
  override def print(value: Hello): String = s"Type: Hello - $value"
}

But next ‘print(int)’ is passed ‘injectedIntPrinter’. Like this, we can inject any instance by importing some instance.

Tagged Type in Scala

Human always makes mistake. Also, software-engineer makes mistake. Guess there are some codes like this.

val userId: Long = 12
val deviceUuid: Long = 1234
val carSerialId: Long = 12345
def getHashCode(
userId: Long,
deviceUuid: Long,
carSerialId: Long
): String = {
val userIdPlus: Long = userId + 1
val deviceUuidPlus: Long = deviceUuid + 2
val carSerialIdPlus: Long = carSerialId + 3
s"$userIdPlus-$deviceUuidPlus-$carSerialIdPlus"
}
getHashCode(userId, deviceUuid, carSerialId) // Right Answer = res0: String = 13-1236-12348
getHashCode(deviceUuid, userId, carSerialId) // Wrong Answer = res1: String = 1235-14-12348
view raw TaggedTypeMistake.sc hosted with ❤ by GitHub

Two function calls


getHashCode(userId, deviceUuid, carSerialId) // Right Answer = res0: String = 13-1236-12348

getHashCode(deviceUuid, userId, carSerialId) // Wrong Answer = res1: String = 1235-14-12348

both compile. But the first one is a right answer, but secondly is wrong. Then What is a good way to help myself not make a mistake? At first thinking, It is very good way to make case class.

case class UserIdCaseClass(userId: Long)
case class DeviceUuidCaseClass(deviceUuid: Long)
case class CarSerialIdCaseClass(carSerialId: Long)
def getHashCodeCaseClass(
userIdCaseClass: UserIdCaseClass,
deviceUuidCaseClass: DeviceUuidCaseClass,
carSerialIdCaseClass: CarSerialIdCaseClass
): String = {
val userIdPlus: Long = userIdCaseClass.userId + 1
val deviceUuidPlus: Long = deviceUuidCaseClass.deviceUuid + 2
val carSerialIdPlus: Long = carSerialIdCaseClass.carSerialId + 3
s"$userIdPlus-$deviceUuidPlus-$carSerialIdPlus"
}
getHashCodeCaseClass(UserIdCaseClass(userId), DeviceUuidCaseClass(deviceUuid), CarSerialIdCaseClass(carSerialId)) // Right Answer = res2: String = 13-1236-12348
getHashCodeCaseClass(DeviceUuidCaseClass(deviceUuid), UserIdCaseClass(userId), CarSerialIdCaseClass(carSerialId)) // Not Even Compiled!

This way, we can reduce mistakes. But we lost many things. First, we have to write many boilerplate. We have to make case class every Id and other Long types. And we lost that Id is the Long type. So, if we want to use userId or carSerialId or DeviceUuid, we have to call member. And cannot assign to Long type variable! To solve these problems, we can use Tagged Type.

Tagged Type

Tagged Type is tagging to some type A and Define as a subtype of A. For example, if we want to define userId as a subtype of Long, we can declare UserId as a Tagged Type of Long. Here is following an example.

import shapeless.tag
import shapeless.tag.@@
trait UserIdTag
trait DeviceUuidTag
trait CarSerialIdTag
type UserId = Long @@ UserIdTag
type DeviceUuid = Long @@ DeviceUuidTag
type CarSerialId = Long @@ CarSerialIdTag
def getHashCodeTagged(userId: UserId, deviceUuid: DeviceUuid, carSerialId: CarSerialId): String = {
val userIdPlus: Long = userId + 1
val deviceUuidPlus: Long = deviceUuid + 2
val carSerialIdPlus: Long = carSerialId + 3
s"$userIdPlus-$deviceUuidPlus-$carSerialIdPlus"
}
val taggedUserId: UserId = tag[UserIdTag][Long](userId)
val taggedDeviceUuid: DeviceUuid = tag[DeviceUuidTag][Long](deviceUuid)
val taggedCarSerialId: CarSerialId = tag[CarSerialIdTag][Long](carSerialId)
getHashCodeTagged(taggedUserId, taggedDeviceUuid, taggedCarSerialId) // Right Answer = res2: String = 13-1236-12348
getHashCodeTagged(taggedDeviceUuid, taggedCarSerialId, taggedUserId) // Not Even Compiled
view raw TaggedTypeAnswer.sc hosted with ❤ by GitHub

Now, UserId, DeviceUuid, CarSerialId is Subtype of Long. We can use this type as Long type. For example, add Number to UserId by just using ‘+’ operator. or can assign to Long. But, It is UserId Type, DeviceUuid cannot be passed as a parameter in UserId Position.

Tagged Type Eraser

Sometimes you want to override function by tagged type like this.

def getId(userId: UserId): Long = userId + 1
def getId(deviceUuid: DeviceUuid): Long = deviceUuid + 2 // Not Compiled because tagged type erased after compile
def getId(carSerialId: CarSerialId): Long = carSerialId + 3 // Not Compiled because tagged type erased after compile

But if you override like this, Compiler says that ‘Not Compiled because tagged type erased after compile’. Yes, Tagged Type is erased after compile time. Then how can we solve the problem? You can solve this by using Either.

def getUserIdOrDeviceIdOrCarSerialId(
id: Either[UserId, Either[DeviceUuid, CarSerialId]]
): Long = id match {
case Left(userId) => userId + 1
case Right(Left(deviceUuid)) => deviceUuid + 2
case Right(Right(carSerialId)) => carSerialId + 3
}
getUserIdOrDeviceIdOrCarSerialId(Left(taggedUserId))
getUserIdOrDeviceIdOrCarSerialId(Right(Left(taggedDeviceUuid)))
getUserIdOrDeviceIdOrCarSerialId(Right(Right(taggedCarSerialId)))
view raw TaggedTypeEither.sc hosted with ❤ by GitHub

Of course, you can use Coproduct in shapeless

import shapeless.{Coproduct, CNil, :+:, Inl, Inr}
type Id = UserId :+: DeviceUuid :+: CarSerialId :+: CNil
def getId(id: Id): Long = id match {
case Inl(userId) => userId + 1
case Inr(Inl(deviceUuid)) => deviceUuid + 2
case Inr(Inr(Inl(carSerialId))) => carSerialId + 3
case Inr(Inr(Inr(cNil))) => 0
}
getId(Coproduct[Id](taggedUserId))
getId(Coproduct[Id](taggedDeviceUuid))
getId(Coproduct[Id](taggedCarSerialId))

By this way, You can keep self from make mistake.

val userId: Long = 12
val deviceUuid: Long = 1234
val carSerialId: Long = 12345
def getHashCode(
userId: Long,
deviceUuid: Long,
carSerialId: Long
): String = {
val userIdPlus: Long = userId + 1
val deviceUuidPlus: Long = deviceUuid + 2
val carSerialIdPlus: Long = carSerialId + 3
s"$userIdPlus-$deviceUuidPlus-$carSerialIdPlus"
}
getHashCode(userId, deviceUuid, carSerialId) // Right Answer = res0: String = 13-1236-12348
getHashCode(deviceUuid, userId, carSerialId) // Wrong Answer = res1: String = 1235-14-12348
// Lets do it by case class
case class UserIdCaseClass(userId: Long)
case class DeviceUuidCaseClass(deviceUuid: Long)
case class CarSerialIdCaseClass(carSerialId: Long)
def getHashCodeCaseClass(
userIdCaseClass: UserIdCaseClass,
deviceUuidCaseClass: DeviceUuidCaseClass,
carSerialIdCaseClass: CarSerialIdCaseClass
): String = {
val userIdPlus: Long = userIdCaseClass.userId + 1
val deviceUuidPlus: Long = deviceUuidCaseClass.deviceUuid + 2
val carSerialIdPlus: Long = carSerialIdCaseClass.carSerialId + 3
s"$userIdPlus-$deviceUuidPlus-$carSerialIdPlus"
}
getHashCodeCaseClass(UserIdCaseClass(userId), DeviceUuidCaseClass(deviceUuid), CarSerialIdCaseClass(carSerialId)) // Right Answer = res2: String = 13-1236-12348
//getHashCodeCaseClass(DeviceUuidCaseClass(deviceUuid), UserIdCaseClass(userId), CarSerialIdCaseClass(carSerialId)) // Not Even Compiled!
import shapeless.tag
import shapeless.tag.@@
trait UserIdTag
trait DeviceUuidTag
trait CarSerialIdTag
type UserId = Long @@ UserIdTag
type DeviceUuid = Long @@ DeviceUuidTag
type CarSerialId = Long @@ CarSerialIdTag
def getHashCodeTagged(
userId: UserId,
deviceUuid: DeviceUuid,
carSerialId: CarSerialId
): String = {
val userIdPlus: Long = userId + 1
val deviceUuidPlus: Long = deviceUuid + 2
val carSerialIdPlus: Long = carSerialId + 3
s"$userIdPlus-$deviceUuidPlus-$carSerialIdPlus"
}
val taggedUserId: UserId = tag[UserIdTag][Long](userId)
val taggedDeviceUuid: DeviceUuid = tag[DeviceUuidTag][Long](deviceUuid)
val taggedCarSerialId: CarSerialId = tag[CarSerialIdTag][Long](carSerialId)
getHashCodeTagged(taggedUserId, taggedDeviceUuid, taggedCarSerialId) // Right Answer = res2: String = 13-1236-12348
//getHashCodeTagged(taggedDeviceUuid, taggedCarSerialId, taggedUserId) // Not Even Compiled
// Can't Override
def getId(userId: UserId): Long = userId + 1
//def getId(deviceUuid: DeviceUuid): Long = deviceUuid + 2 // Not Compiled because tagged type erased after compile
//def getId(carSerialId: CarSerialId): Long = carSerialId + 3 // Not Compiled because tagged type erased after compile
// Let's do by either
def getUserIdOrDeviceIdOrCarSerialId(
id: Either[UserId, Either[DeviceUuid, CarSerialId]]
): Long = id match {
case Left(userId) => userId + 1
case Right(Left(deviceUuid)) => deviceUuid + 2
case Right(Right(carSerialId)) => carSerialId + 3
}
getUserIdOrDeviceIdOrCarSerialId(Left(taggedUserId))
getUserIdOrDeviceIdOrCarSerialId(Right(Left(taggedDeviceUuid)))
getUserIdOrDeviceIdOrCarSerialId(Right(Right(taggedCarSerialId)))
// You can do by Coproduct by shapeless
import shapeless.{Coproduct, CNil, :+:, Inl, Inr}
type Id = UserId :+: DeviceUuid :+: CarSerialId :+: CNil
def getId(id: Id): Long = id match {
case Inl(userId) => userId + 1
case Inr(Inl(deviceUuid)) => deviceUuid + 2
case Inr(Inr(Inl(carSerialId))) => carSerialId + 3
case Inr(Inr(Inr(cNil))) => 0
}
getId(Coproduct[Id](taggedUserId))
getId(Coproduct[Id](taggedDeviceUuid))
getId(Coproduct[Id](taggedCarSerialId))
view raw TaggedType.sc hosted with ❤ by GitHub

Scala Implicitly

As I study about Scala, I open see implicitly. But, every time I see this, I just ignore just thinking like “It’s syntactic sugar of implicit”. This time, I write this post to really under stand this.

In Scala, implicitly is just this.


def implicitly[T](implicit e: T) = e

That’s all! implicitly is a function just get implicit e instance and return e. Then, what is an advantage of using implicitly?

Example 1

You can use implicit like this.

implicit val optionInt: Option[Int] = Some(1)
implicit val optionBoolean: Option[Boolean] = Some(true)

def getImplicitInt(implicit oInt: Option[Int]): Int = oInt.get
def getImplicitBoolean(implicit oBoolean: Option[Boolean]): Boolean = oBoolean.get

getImplicitInt
// res0: Int = 1
getImplicitBoolean
// res1: Boolean = true

Now, you can use just same by using implicitly


implicit val optionInt: Option[Int] = Some(1)
implicit val optionBoolean: Option[Boolean] = Some(true)

def getImplicitlyA[A: Option]: A = implicitly[Option[A]].get

getImplicitlyA[Int]
// res2: Int = 1
getImplicitlyA[Boolean]
// res3: Boolean = true

Implicitly can use in such situation.

Implicitly can use when you want to get implicit instance which have 1 type parameter.

As you can see above, by using ‘Implicitly[Option[Int]]’, you can get implicit instance ‘Option[Int]’. It has 1 type parameter Int.

Example 2

Then how about implicitly for the type which has no type parameter? For example, implicit can do like this.


implicit val string: String = "Hello"
implicit val boolean: Boolean = true
implicit val int: Int = 0

def getImplicitT[T](implicit t: T): T = t

getImplicitT[Int]
// res0: Int = 0

getImplicitT[String]
// res1: String = Hello

getImplicitT[Boolean]
// res2: Boolean = true

But, if you do the same with implicitly, errors occur.


def getImplicitlyT[T]: T = implicitly[T]

getImplicitlyT[Int]
getImplicitlyT[String]
getImplicitlyT[Boolean]

// compile error: not enough arguments for method implicitly: (implicit e: T)T. Unspecified value parameter e. def getImplicitlyT[T]: T = implicitly[T] ^

Yes, because I mentioned above, implicitly can be used the type which has a type parameter. Then how can we use implicitly in this situation? The answer is ‘make Id type’.


type Id[A] = A

def getImplicitlyT[T : Id]: T = implicitly[Id[T]]

getImplicitlyT[Int]
// res3: Int = 0

getImplicitlyT[String]
// res4: String = Hello

getImplicitlyT[Boolean]
// res5: Boolean = true

I make new type Id which has one type parameter. And Id[A] is same as A. So, you can use just like implicit

Context bound

Right side of function ‘getImplicitlyAt’ has type parameter(A : Option). It looks like type bound. But it is called Context bound. Type bound is ‘<:’.You can see type bound in Scala School. In context bound, it doesn’t mean that A is Option. Instead, A will be a type passing to Option.

implicit val optionInt: Option[Int] = Some(1)
implicit val optionBoolean: Option[Boolean] = Some(true)
def getImplicitOptionInt(implicit oInt: Option[Int]): Int = oInt.get
def getImplicitOptionBoolean(implicit oBoolean: Option[Boolean]): Boolean = oBoolean.get
getImplicitOptionInt
getImplicitOptionBoolean
def getImplicitlyOptionA[A: Option]: A = implicitly[Option[A]].get
getImplicitlyOptionA[Int]
getImplicitlyOptionA[Boolean]
trait Parent {
def sayHello: String
}
trait Son extends Parent {
override def sayHello: String = "Hello! I'm Son!"
}
trait Daughter extends Parent {
override def sayHello: String = "Hello! I'm Daughter!"
}
def sayHello[A <: Parent](a: A): Unit = println(a.sayHello)
sayHello(new Son {})
sayHello(new Daughter {})
view raw ImplicitlyExample.sc hosted with ❤ by GitHub
implicit val string: String = "Hello"
implicit val boolean: Boolean = true
implicit val int: Int = 0
def getImplicitT[T](implicit t: T): T = t
getImplicitT[Int]
getImplicitT[String]
getImplicitT[Boolean]
type Id[A] = A
def getImplicitlyT[T : Id]: T = implicitly[Id[T]]
getImplicitlyT[Int]
getImplicitlyT[String]
getImplicitlyT[Boolean]

Applicative in Scala Cats

I always heard about Applicative, but I don’t know what really is. So, I write this post to understand myself. Below picture is from adit.io and example is from mastering advanced Scala.

What is Applicative?

During programming, you will encounter this situation.

def getSome(a: Int): Option[Int] = Some(a)

def getNone(a: Int): Option[Int] = None

def add(a: Int, b: Int): Int = a + b

val aOpt = getSome(1)

val bOpt = getSome(2)

And I want to get aOpt and bOpt add together using add. But aOpt and bOpt is Option. So, the usual way to do is by using flatMap and map like this.


aOpt.flatMap(a => bOpt.map(b => add(a, b)))
// res0: Option[Int] = Some(3)

In here, we can use Applicative.

What is Applicative?

I don’t know that my understanding of Applicative is right. But I think, Applicative is

applicative_just.png

Very famous picture about Applicative.

In here there is a wrapped value: 2. And there is a wrapped function. We unwrapped both function and value, and process some operation and wrap it again! And we can also adapt these things to above example.


import cats.instances.option._
import cats.Applicative

Applicative[Option].map2(aOpt, bOpt)(add)
// res1: Option[Int] = Some(3)

We can also use like this.


import cats.syntax.all._

(aOpt |@| bOpt).map(add)

Monad extends Applicative


@typeclass trait Monad[F[_]] extends FlatMap[F] with Applicative[F] {
  override def map[A, B](fa: F[A])(f: A => B): F[B] =
  flatMap(fa)(a => pure(f(a)))
}

As you can see, Monad extends Applicative. And Applicative extends Functor. So, hierarchy is ‘Monad <: Applicative <: Functor’

Tip – Traverse

While studying with Mastering Advanced Scala, there are some tips in Applicative, So, I write down this. If there is a List[Int] and want all element adapt ‘getSome’ function, for example


val ints = List(1,2,3,4,5)
ints.map(getSome)
// res3: List[Option[Int]] = List(Some(1), Some(2), Some(3), Some(4), Some(5))

The result is ‘List(Some(1), Some(2), Some(3), Some(4), Some(5))’ and if you want to make ‘Some(List(1, 2, 3, 4, 5))’ you can use Traverse like this.


import cats.Traverse
import cats.instances.list._
import cats.instances.option._

Traverse[List].traverse(ints)(getSome)

// res4: Option[List[Int]] = Some(List(1, 2, 3, 4, 5))

This will return None if any element return None


def getSomeOrNone(a: Int): Option[Int] =
  if(a % 2 == 0) Some(a)
  else None

Traverse[List].traverse(ints)(getSomeOrNone)

// res5: Option[List[Int]] = None

 

def getSome(a: Int): Option[Int] = Some(a)
def getNone(a: Int): Option[Int] = None
def add(a: Int, b: Int): Int = a + b
val aOpt = getSome(1)
val bOpt = getSome(2)
aOpt.flatMap(a => bOpt.map(b => add(a, b)))
import cats.instances.option._
import cats.Applicative
Applicative[Option].map2(aOpt, bOpt)(add)
import cats.syntax.all._
(aOpt |@| bOpt).map(add)
val ints = List(1,2,3,4,5)
ints.map(getSome)
import cats.Traverse
import cats.instances.list._
import cats.instances.option._
Traverse[List].traverse(ints)(getSome)
def getSomeOrNone(a: Int): Option[Int] =
if(a % 2 == 0) Some(a)
else None
Traverse[List].traverse(ints)(getSomeOrNone)

Cartesian Product in Scala Cats

During work, I have some problem handling three future at the same time. First future return a long value and second and third future receive the parameter from the first return and return a tuple.

The code is like this.


for {
  long <- futureLong()
  int <- futureInt(long)
  char <- futureChar(long)
} yield (int, char)

Of course for comprehension operates step by step, so, futureInt is called after futureLong is finished and futureChar is called after futureInt is finished. So, It takes too much time to get a final return value.

So, I changed codes like this.


val longVal: Future[Long] = futureLong()

val intVal: Future[Int] = for {
  long <- futureLong()
  int <- futureInt(long)
} yield int

val charVal: Future[Char] = for {
  long <- futureLong()
  char <- futureChar(long)
} yield char

val forComp: (Int, Char) = Await.result(for {
  int <- intVal
  char <- charVal
} yield (int, char), 5 second)

Yes, It works as I want. But, there is duplicated codes like, ‘long <- futureLong()’. During thinking a long time, my coworker, Liam, recommends using  Cartesian Product.

What is Cartesian Product?

Cartesian Product is very simple. It’s just a product of two Sets. For example, if there are two sets A = {1,2,3} and B = {‘a’, ‘b’, ‘c’}, Cartesian Product of A X B is

{(1, ‘a’), (1, ‘b’), (1, ‘c’), (2, ‘a’), (2, ‘b’), (2, ‘c’), (3, ‘a’), (3, ‘b’), (3, ‘c’)}

Yes. that’s all. Of course Cartesian Product didn’t hold commutative law. For example Cartesian Product of B X A is

{(‘a’, 1), (‘a’, 2), (‘a’, 3), (‘b’, 1), (‘b’, 2), (‘b’, 3), (‘c’, 1), (‘c’, 2), (‘c’, 3)}

And different from A X B.

Cartesian Product in cats

Above example can be implemented in Scala.


import cats.instances.list._
import cats.syntax.all._

val ints: List[Int] = List(1, 2, 3, 4)
val chars: List[Char] = List('a', 'b', 'c')

(ints |@| chars).tupled
// res0: List[(Int, Char)] = List((1,a), (1,b), (1,c), (2,a), (2,b), (2,c), (3,a), (3,b), (3,c), (4,a), (4,b), (4,c))
(chars |@| ints).tupled
// res1: List[(Char, Int)] = List((a,1), (a,2), (a,3), (a,4), (b,1), (b,2), (b,3), (b,4), (c,1), (c,2), (c,3), (c,4))

(ints |@| chars).map(_ + _.toString)
// res2: List[String] = List(1a, 1b, 1c, 2a, 2b, 2c, 3a, 3b, 3c, 4a, 4b, 4c)

In Cats Cartesian Product Syntax is ‘|@|’.(Some people call it Oink, and others call Pizza box) If you call tuple, it will return List Tuple. You can also map it.

Cartesian Product in Future

Ok, let’s return to the previous problem. I need to call futureInt and futureChar at a same time. And using three for comprehension is too dirty. So, how and I solve this with Cartesian Product? It’s very simple.


for {
  long <- futureLong()
  (int, char) <- (futureInt(long) |@| futureChar(long)).tupled
} yield (int, char)

Very simple right? You can test whether this expression really operates at the same time.

package org.example.ktz.blog
import cats.syntax.all._
import cats.instances.future._
import org.scalatest._
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration._
import scala.concurrent.{Await, Future}
class CartesianProductTest extends FlatSpec with Matchers {
def futureLong(): Future[Long] = Future {
Thread.sleep(2000)
println("Future Long Done!")
2
}
def futureInt(long: Long): Future[Int] = Future{
Thread.sleep(2000)
println("Future Int Done!")
long.toInt
}
def futureChar(long: Long): Future[Char] = Future{
Thread.sleep(2000)
println("Future Char Done!")
long.toChar
}
"Concurrent" should "for comprehension" in {
val forComp: (Int, Char) = Await.result(for {
long <- futureLong()
int <- futureInt(long)
char <- futureChar(long)
} yield (int, char), 5 second)
// java.util.concurrent.TimeoutException: Futures timed out after [5 seconds]
}
"Concurrent" should "for comprehension2" in {
val longVal: Future[Long] = futureLong()
val intVal: Future[Int] = for {
long <- futureLong()
int <- futureInt(long)
} yield int
val charVal: Future[Char] = for {
long <- futureLong()
char <- futureChar(long)
} yield char
val forComp: (Int, Char) = Await.result(for {
int <- intVal
char <- charVal
} yield (int, char), 5 second)
// Work well 🙂
}
"Concurrent" should "cartesian" in {
val cartesion: (Int, Char) = Await.result(for {
long <- futureLong()
(int, char) <- (futureInt(long) |@| futureChar(long)).tupled
} yield (int, char), 5 second)
// Work well 🙂
}
}

In this test, the first test occurs TimeoutException and second and third pass test well. Then, we can make a conclusion that, last expression also run at the same time.

Writer Monad in cats

I already wrote a post about reader monad and It’s time to write about Writer Monad. First, I thought that Writer Monad is opposite of Reader Monad. But it’s quite different from Reader Monad. Let’s say that Writer Monad is…

Writer Monad is stack something while operating.

Hm… I think it’s not a very well summarized sentence. If any reader has nicer summarized one, comment me. Below example is from mastering advanced Scala.

Example

type Writer id defined in cats like this.

type Writer[L, V] = WriterT[Id, L, V]

As you can see, Writer Monad accept type L & V. type L is a type which stacks something. and V is a result(return) value. Id position in WirterT is something wraps the result. Here is Id so, never mind.

You can simply use Writer Monad like.

import cats.data.Writer

def greetW(name: String, logged: Boolean): Writer[List[String], String] =
  Writer(List("Compose a greeting"), {
    val userName = if(logged) name else "User"
    s"Hello $userName"
  })

def isLoggedW(name: String): Writer[List[String], Boolean] =
  Writer(List("Checking if user is logged in"), name.length == 3)

import cats.instances.list._

val name = "Joe"

val resultW: Writer[List[String], String] = for {
  logged <- isLoggedW(name)
  greeting <- greetW(name, logged)
} yield greeting
val (log, result) = resultW.run

// log: List[String] = List(Checking if user is logged in, Compose a greeting)
//result: String = Hello Joe

Yes. It’s very simple. It just stacks some strings in L(List[String]) and returns value in V(String). I think it’s very useful.

How is it works?

Of course, you can have curious that how’s is it works? As you can see in ‘for comprehension’, there is nowhere to pass List[String]. But after finishing for comprehension, it splits out log: List[String] and result: String.

Let’s remind this.

For comprehension is syntactic sugar of flatMap.

You can also write like this.

val flatMapResult: Writer[List[String], String] =
  isLoggedW(name).flatMap(logged => greetW(name, logged))

It’s the same result of for comprehension statements.

Ok. It’s flatMap. Then, Let’s deep dive to WriterT.flatMap

final case class WriterT[F[_], L, V](run: F[(L, V)]) {
        ...
  def flatMap[U](f: V => WriterT[F, L, U])(implicit flatMapF: FlatMap[F], semigroupL: Semigroup[L]): WriterT[F, L, U] =
    WriterT {
      flatMapF.flatMap(run) { lv =>
        flatMapF.map(f(lv._2).run) { lv2 =>
          (semigroupL.combine(lv._1, lv2._1), lv2._2)
        }
      }
    }
        ...
}

In this code, never mind another but line:7. In Line 7, they combine two List[String]. Stacking is occurred in here. WriterT is different from others. value run’s type is F[(L, V)] and it does flatMap with it. So we can stack List[String]

WriterT Example

WriterT is Monad Transformer of Writer. So, you can use like this.

import cats.data.WriterT
import cats.instances.future._
import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global

def greetWFuture(name: String, logged: Boolean): WriterT[Future, List[String], String] =
  WriterT(Future(List("Compose a greeting"), {
    val userName = if(logged) name else "User"
    s"Hello $userName"
  }))

def isLoggedWFuture(name: String): WriterT[Future, List[String], Boolean] =
  WriterT(Future(List("Checking if user is logged in"), name.length == 3))

val resultWFuture: WriterT[Future, List[String], String] = for {
  logged <- isLoggedWFuture(name)
  greeting <- greetWFuture(name, logged)
} yield greeting

val futureResult: Future[(List[String], String)] = resultWFuture.run

import scala.concurrent.Await
import scala.concurrent.duration._

val (logAwait, resultAwait) = Await.result(futureResult, 2 second)

It’s very simple. We can use Writer Monad inside another Monad.

import cats.data.Writer
def greetW(name: String, logged: Boolean): Writer[List[String], String] =
Writer(List("Compose a greeting"), {
val userName = if(logged) name else "User"
s"Hello $userName"
})
def isLoggedW(name: String): Writer[List[String], Boolean] =
Writer(List("Checking if user is logged in"), name.length == 3)
import cats.instances.list._
val name = "Joe"
val resultW: Writer[List[String], String] = for {
logged <- isLoggedW(name)
greeting <- greetW(name, logged)
} yield greeting
val (log, result) = resultW.run
val flatMapResult: Writer[List[String], String] = isLoggedW(name).flatMap(logged => greetW(name, logged))
import cats.data.WriterT
import cats.instances.future._
import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
def greetWFuture(name: String, logged: Boolean): WriterT[Future, List[String], String] =
WriterT(Future(List("Compose a greeting"), {
val userName = if(logged) name else "User"
s"Hello $userName"
}))
def isLoggedWFuture(name: String): WriterT[Future, List[String], Boolean] =
WriterT(Future(List("Checking if user is logged in"), name.length == 3))
val resultWFuture: WriterT[Future, List[String], String] = for {
logged <- isLoggedWFuture(name)
greeting <- greetWFuture(name, logged)
} yield greeting
val futureResult: Future[(List[String], String)] = resultWFuture.run
import scala.concurrent.Await
import scala.concurrent.duration._
val (logAwait, resultAwait) = Await.result(futureResult, 2 second)
view raw WriterExample.sc hosted with ❤ by GitHub

Reader Monad in cats

After studying Monad, I also curious about Reader Monad and Writer Monad. So, I write a post about it. In fact, first, I suppose to write a Reader Monad and Writer Monad at the same post, but later I decided to write separately.

Below example is from mastering advanced Scala

What is Reader Monad

At first, I thought Reader Monad is used to read something. It’s true. But another definition is effective.

Reader Monad is used to Dependency Injection.

Yes. I think it’s a better definition. Reader Monad object is like this.

object Reader {
  def apply[A, B](f: A => B): Reader[A, B] = ReaderT[Id, A, B](f)
}

Anonymous function f is the function we want to run. And ‘A’ will be injected later when we call like this.


readerMoandValue.run(ATypeInstance)

ATypeInstance is an instance which type is A. And as you can see, is passed when you call run function.

Example

First, Let’s suppose some Service like this.

trait AuthService {
  def isLogged(name: String): Boolean
}

class AuthServiceChar3 extends AuthService{
  override def isLogged(name: String): Boolean = name.length == 3
}

class AuthServiceChar5 extends AuthService{
  override def isLogged(name: String): Boolean = name.length == 5
}

trait UserService {
  def greet(name: String, isLogged: Boolean): String
}

class UserServiceDefaultUser extends UserService{
  override def greet(name: String, isLogged: Boolean): String = {
    val actualName = if(isLogged) name else "User"

    s"Hello $actualName"
  }
}

class UserServiceNoDefault extends UserService{
  override def greet(name: String, isLogged: Boolean): String = {
    if(isLogged) s"Hello $name" else "No authorization"
  }
}

case class Environment(userName: String, userService: UserService, authService: AuthService)

AuthService has a simple auth logic and actualized class is AuthServiceChar3 and AuthServiceChar5. And UserSertivce has a simple greet logic and actualized class is UserServiceDefaultUser and UserServiceNoDefault.

And Environment is case class which needs a user name, UserService, and AuthService. And here are some Reader Monad use environment and return something.

import cats.data.Reader

def isLoggedUser: Reader[Environment, Boolean] = Reader[Environment, Boolean] { env =>
  env.authService.isLogged(env.userName)
}

def greetUser(logged: Boolean): Reader[Environment, String] = Reader[Environment, String] { env =>
  env.userService.greet(env.userName, logged)
}
// In Intellij if you use auto complete, Kleisli[Id, Environment, String] will be written. I will write post later
val resultR: Reader[Environment, String] = for {
  logged <- isLoggedUser
  greeting <- greetUser(logged)
} yield greeting

Value resultR is a Reader Monad get logged value from isLoggedUser and return greet String.

In here yet, we didn’t pass Environment instance. Now, let’s pass Environment.


val environment1 = Environment("Joe", new UserServiceDefaultUser, new AuthServiceChar3)

println(resultR.run(environment1))  // print Hello Joe

val environment2 = Environment("Joe", new UserServiceNoDefault, new AuthServiceChar5)

println(resultR.run(environment2))  // print No authorization

As you can see, we inject environment when we call run function. And when we inject different instance, print different output.

trait AuthService {
def isLogged(name: String): Boolean
}
class AuthServiceChar3 extends AuthService{
override def isLogged(name: String): Boolean = name.length == 3
}
class AuthServiceChar5 extends AuthService{
override def isLogged(name: String): Boolean = name.length == 5
}
trait UserService {
def greet(name: String, isLogged: Boolean): String
}
class UserServiceDefaultUser extends UserService{
override def greet(name: String, isLogged: Boolean): String = {
val actualName = if(isLogged) name else "User"
s"Hello $actualName"
}
}
class UserServiceNoDefault extends UserService{
override def greet(name: String, isLogged: Boolean): String = {
if(isLogged) s"Hello $name" else "No authorization"
}
}
case class Environment(userName: String, userService: UserService, authService: AuthService)
import cats.data.Reader
def isLoggedUser: Reader[Environment, Boolean] = Reader[Environment, Boolean] { env =>
env.authService.isLogged(env.userName)
}
def greetUser(logged: Boolean): Reader[Environment, String] = Reader[Environment, String] { env =>
env.userService.greet(env.userName, logged)
}
// In Intellij if you use auto complete, Kleisli[Id, Environment, String] will be written. I will write post later
val resultR: Reader[Environment, String] = for {
logged <- isLoggedUser
greeting <- greetUser(logged)
} yield greeting
val environment1 = Environment("Joe", new UserServiceDefaultUser, new AuthServiceChar3)
println(resultR.run(environment1))
val environment2 = Environment("Joe", new UserServiceNoDefault, new AuthServiceChar5)
println(resultR.run(environment2))
view raw ReaderExample.sc hosted with ❤ by GitHub

Pattern Matching & unapply & apply

In Scala, we use pattern matching very often. It’s made by unapply. But because we use this by making case class, I always forget how to make it. I write this post to remind me. All example uploaded in below(gist).

How to make apply & unapply

Normal Pattern Matching

Let’s say that there is a class defined like this.


class Person(val name: String, val age: Int, val weight: Int)

And we can write apply by making companion object.


object Person {
  def apply(name: String, age: Int, weight: Int): Person = new Person(name, age, weight)
}

We can also write unapply in companion object like this.


object Person {
  def apply(name: String, age: Int, weight: Int): Person = new Person(name, age, weight)

  def unapply(arg: Person): Option[(String, Int, Int)] =
    if(arg.age > 20) Some(arg.name, arg.age, arg.weight)
    else None
}

Unapply return Option Tuple. After the operation, if it returns Some, it matches to case class. If it returns None, it does not match. We can use it like this.

val young: Person = Person("Martin Junior", 14, 50)
val old: Person = Person("Martin", 29, 70)

def PatternMatching(person: Person): Unit = person match {
  case Person(name, age, weight) => println(s"name: $name, age: $age, weight: $weight")
  case _ => println(s"${person.name} is under 20")
}

In apply, If age is under 20 it returns None so, young is not matched. And old is matched. Yes! it’s simple! But I have additional Curious.

How about pattern matching partial?

What if I don’t want to expose weight in pattern matching? It’s simple!

class SecretPerson(val name: String, val age: Int, val weight: Int)

object SecretPerson {
  def apply(name: String, age: Int, weight: Int): SecretPerson = new SecretPerson(name, age, weight)

  def unapply(arg: SecretPerson): Option[(String, Int)] =
  if(arg.age >= 20) Some(arg.name, arg.age)
  else None
}

def PatternMatchingSecret(secretPerson: SecretPerson): Unit = secretPerson match {
  case SecretPerson(name, age) => println(s"name: $name, age: $age")
  case _ => println(s"${secretPerson.name} is under 20")
}

val secretYoung: SecretPerson = SecretPerson("Martin Junior", 14, 50)
val secretOld: SecretPerson = SecretPerson("Martin", 29, 70)

PatternMatchingSecret(secretYoung)
PatternMatchingSecret(secretOld)

Just return Option Tuple without weight. It is possible!

Case class unapply overriding

How about case class? It already has pre-defined unapply. What if I also want to hide weight this time? Let’s do it!


case class CasePerson(name: String, age: Int, weight: Int)
object CasePerson {
//  Compile Error - unapply is defined twice
//  def unapply(arg: CasePerson): Option[(String, Int)] =
//    if(arg.age >= 20) Some(arg.name, arg.age)
//    else None
}

val caseYoung: CasePerson = CasePerson("Martin Junior", 14, 50)
val caseOld: CasePerson = CasePerson("Martin", 29, 70)

def PatternMatchingCase(casePerson: CasePerson): Unit = casePerson match {
//   case CasePerson(name, age) => println(s"name: $name, age: $age") compile error: wrong number of arguments
  case CasePerson(name, age, weight) => println(s"name: $name, age: $age, weight: $weight")
  case _ => println(s"${casePerson.name} is under 20")
}

It’s impossible. We can get compile error in override ‘unapply is defined twice’. And also in case matching part ‘wrong number of arguments’.

class Person(val name: String, val age: Int, val weight: Int)
object Person {
def apply(name: String, age: Int, weight: Int): Person = new Person(name, age, weight)
def unapply(arg: Person): Option[(String, Int, Int)] =
if(arg.age >= 20) Some(arg.name, arg.age, arg.weight)
else None
}
val young: Person = Person("Martin Junior", 14, 50)
val old: Person = Person("Martin", 29, 70)
def PatternMatching(person: Person): Unit = person match {
case Person(name, age, weight) => println(s"name: $name, age: $age, weight: $weight")
case _ => println(s"${person.name} is under 20")
}
PatternMatching(young)
PatternMatching(old)
class SecretPerson(val name: String, val age: Int, val weight: Int)
object SecretPerson {
def apply(name: String, age: Int, weight: Int): SecretPerson = new SecretPerson(name, age, weight)
def unapply(arg: SecretPerson): Option[(String, Int)] =
if(arg.age >= 20) Some(arg.name, arg.age)
else None
}
def PatternMatchingSecret(secretPerson: SecretPerson): Unit = secretPerson match {
case SecretPerson(name, age) => println(s"name: $name, age: $age")
case _ => println(s"${secretPerson.name} is under 20")
}
val secretYoung: SecretPerson = SecretPerson("Martin Junior", 14, 50)
val secretOld: SecretPerson = SecretPerson("Martin", 29, 70)
PatternMatchingSecret(secretYoung)
PatternMatchingSecret(secretOld)
case class CasePerson(name: String, age: Int, weight: Int)
object CasePerson {
// Compile Error - unapply is defined twice
// def unapply(arg: CasePerson): Option[(String, Int)] =
// if(arg.age >= 20) Some(arg.name, arg.age)
// else None
}
val caseYoung: CasePerson = CasePerson("Martin Junior", 14, 50)
val caseOld: CasePerson = CasePerson("Martin", 29, 70)
def PatternMatchingCase(casePerson: CasePerson): Unit = casePerson match {
// case CasePerson(name, age) => println(s"name: $name, age: $age") compile error: wrong number of arguments
case CasePerson(name, age, weight) => println(s"name: $name, age: $age, weight: $weight")
case _ => println(s"${casePerson.name} is under 20")
}
view raw ApplyNUnapply.sc hosted with ❤ by GitHub

Covariant and Contravariant in Scala

Scala often uses type parameter and also, you can see [+A] or [-A] in many library codes. I also wonder what it is. So, I study this and discuss with my co-worker. And leave this post to understand me. (But also, any comments are always welcome!)

Special Thanks for Karellen in Kakao.

What is Covariant, Contravariant and Invariant?

In Scala School Co, Contra, In -variant is described like this.

Variance is  about “if A <: B ( A is a subtype of B), how is the relation between M[A] and M[B]?”

if A <: B… then,

Meaning Scala notation
covariant M[A] <: M[B] [+B]
contravariant M[B] <: M[A] [-B]
invariant M[B] and M[A] are not related [B]

This means for example, in Java

package org.ktz.example.blog;
import java.util.ArrayList;
import java.util.List;
public class Variance {
public static void main(String[] args) {
String str = "";
Object obj = str;
List<String> listStr = new ArrayList<>();
// List<Object> objects = listStr; // compile error
}
}
view raw Variance.java hosted with ❤ by GitHub

As you can see above,

List<Object> objects = listStr

cannot be compiled.

Because in Java List<Object> is not considered as a parent of List<String>.

But, in Scala,

val obj: Object = "Hello World!"
val listString: List[String] = List.empty
val listAny: List[Any] = listString
view raw Variance.scala hosted with ❤ by GitHub

It can be compiled. Because in Scala, List[Any] is considered as a parent of  List[String]. In Scala source code, List is set as covariant.

type List[+A] = scala.collection.immutable.List[A]
view raw ScalaList.scala hosted with ❤ by GitHub

Liskov Substitution Principle

It is very natural and simple. If A <: B, we can assign Typ A variable to Type B variable.


val a: A = new A

val b: B = a

also, List[+A] can be adapted, becase List is covariant.


val a: List[A] = List[A].empty

val b: List[B] = a

Variance in class

Guess, that I want to make my own List, and class like this.


class MyList[+A] {
    def insert(element: A): A = ??? // compile error
}

compile error message is


Error: covariant type A occurs in contravariant position in type A of value element
def insert(element: A): A = ???

Why does that happen? Simply thinking, I make a List of type A and subtype of A can be considered as a subtype of MyList[A]. Below topic explain of this.

Rethinking of + and –

In Scala, ‘+’ means Covariant and ‘-‘ means Contravariant. Let`s forget about it. And redefine ‘+’ as ‘Can receive subtype’ and ‘-‘ as ‘Can receive Supertype’. And then, above error can be described.

As defined in Liskov principle, In element, we can assign super type of A. But, +A is covariant. So, element can be only assigned subtype. So, this is contradiction.

So, if we correct the code like this, It can be compiled.


class MyList[+A] {
    def insert[B >: A](element: B): A = ???
}

By the way, how about return type as A. Is this ok? Of course yes. Let`s say that C <: A

And after long, long operation, the return type is decided as C. And as Liskov law, type C can be assigned to type A.(val c: C = A)

Variance in Function

In conclusion(as you can see above), Parameter is Contravariant(-A) and return type is Covariant. Also, in Scala, Function signature is


trait Function1[-T1, +R]

Yes, it`s simplified.

The subtype of Functions

In Scala course in coursera, there is a interesting problem. (It`s motivation of this post)

There are types like this.


NoneEmpty <: IntSet

type A = IntSet => NoneEmpty

type B = NoneEmpty => IntSet

What is the relationship between A and B?

  • A <: B
  • A >: B

The answer is A <: B. Why? As Liskov Principle, A can be assigned as B. It does not feel the impact to me. So, Let`s say that

if A <: B, A can pretend as B

Yes, String can pretend as Object. And above, NoneEmpty can pretend as IntSet.

Ok, then type A (IntSet => NoneEmpty) can pretend as type B(NoneEmpty => IntSet)? Of course!

If User passes NoneEmpty to A, It can be assigned to IntSet. And after an operation, A return NoneEmpty. And NoneEmpty can be assigned to IntSet.

subtypeFunction.png

A can pretend as B and A <: B