A Scala tutorial for Java developers

A Scala tutorial for Java developers

A Scala tutorial for Java developers

Introduction

I was asked to prepare a 30-min presentation about the Scala programminglanguage. The target audience is a community of experienced Java developers in our organization. Taking into account the time limit and the audience type, I will go straight to the business an I will cover the core Scala features.

So, Scala was first introduced in January 2004 by Martin Odersky, it is JVM based and statically typed programming language. Scala supports both object-oriented and functional programming paradigms. The most well-known products written in Scala are Apache Spark, Apache Kafka, Apache Flink. And finally, Scala scores pretty well in programming language popularity rankings (13)

Scala pros: what makes it great

Concise syntax

Scala is designed to be concise, many of Scala’s design decisions aimed to address the verbosity of Java. For example here is the code that defines new class ***UserInfo. ***This class has two properties. The first one is read-write property ***Name ***and the second one is **BirthDate **which is read-only.

Java code:

class UserInfo {
    private String name;
    private LocalDate birthDate;

    public UserInfo(String name, LocalDate birthDate) {
        this.name = name;
        this.birthDate = birthDate;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public LocalDate getBirthDate() {
        return birthDate;
    }
}

Scala code:

class UserInfo(var name: String, val birthDate: LocalDate)

Just a bit about syntax, in Scala variable name, comes first and then comes variables type. A var is a variable. It’s a mutable reference to a value. On the other hand, val is a value, an immutable reference. In this single line, you can see the expressiveness of Scala.

Case Classes

When it comes to comparing objects in Java, it compares references. For example, the following code will return false.

LocalDate date = LocalDate.now();
UserInfo a = new UserInfo("John", date);
UserInfo b = new UserInfo("John", date);
return (a == b);

But sometimes we would like to compare objects by underlying values and not by reference. So in Java, we have to implement **equals **and hashcodemethods. In Scala, we have **Case Classes. **Case Class automatically defines **equals, hashcode **methods and getters for constructor arguments. And most important it can be used in pattern matching. So by implementing UserInfo as a Case Class, we will get the following result:

case class UserInfo(var name: String, birthDate: LocalDate)
val date = LocalDate.now()
val a = new UserInfo("John", date)
val b = new UserInfo("John", date)
a == b // returns True

Pattern matching

Pattern matching is a mechanism for checking a value against a pattern. You can think about Scala’s pattern matching as a more powerful version of **switch statement **in Java. A match expression has a value, the <strong>match</strong>keyword, and at least one <strong>case</strong>clause. Example:

def matchTest(x: Int): String = x match {
  case 1 => "one"
  case 2 => "two"
  case _ => "many"
}

In Scala it is possible to perform pattern matching on types:

def matchOnType(x: Any): String = x match {
  case x: Int => s"$x is integer"
  case x: String => s"$x is string"
  case _ => "unknown type"
}
matchOnType(1) // returns 1 is integer
matchOnType("1") // returns 1 is string

Let’s see the more powerful case. Let’s match integers sequence

def matchList(x: List[Int]): String = x match {
  case List(_) => "a single element list"
  case List(_, _) => "a two elements list"
  case List(1, _*) => "a list starting with 1"
}
matchList(List(3)) // returns a single elements list matchList(List(0, 1)) // returns a two elements list
matchList(List(1, 0, 0)) // returns a list starting with 1

And finally, let’s see the most powerful case: a pattern matching on case classes.

case class UserInfo(var name: String, birthDate: LocalDate)
def isUserJohn(x: Any): Boolean = x match {
  case UserInfo("John", _) => true
  case _ => false
}
val list = List(
  "wrong user type",
  UserInfo("Ben", LocalDate.now()),
  UserInfo("John", LocalDate.now()))
list.filter(isUserJohn) // list with one element UserInfo John

Implicit Classes

The implicit keyword makes the class’s primary constructor available for implicit conversions when the class is in scope.

Assume that you were asked to extend UserInfo class with a getAge method. So there are two options. Create a separate library of UserInfo utility methods, like an ***UserInfoUtil ***class or to create a subclass which inherits all attributes and methods of UserInfo and extends it with ***getAge. ***In Scala, you can add your own behavior(s) by using implicit class.

object Extensions {
  implicit class UserInfoExt(user: UserInfo) {
    def getAge(): Int = {
      Years.yearsBetween(LocalDate.now(), user.birthDate,).getYears
    }
  }
}

This will let you write the code like this

import Extensions._
val user = new UserInfo("John", LocalDate.now())
user.getAge()

Higher-order functions

Scala allows the definition of higher-order functions. These are functions that take other functions as parameters, or whose result is a function. The classic examples of higher-order functions are map and filter.

**Map **applies a function on all elements of a collection. For example, let’s multiply by 2 each element of a given list

def multiply(x: Int): Int = x * 2

List(1, 2, 3).map(multiply) // returns 2 4 6 

**Filter **creates a list of elements for which a function returns true. Here is a short and concise example:

def isEven(x: Int): Boolean = x % 2 == 0
List(1, 2, 3).filter(isEven) // returns 2

Let’s define our own higher-order function:

def apply(f: Int => String, x: Int): String = f(x)
def printInt(x: Int): String = s"Printing integer $x"
apply(printInt, 3)

Option Monad

Monad is a design pattern that allows structuring programs generically while automating away boilerplate code needed by the program logic and provides an easy way for composing and sequencing operations on some contained value(s).

I would like to mention that I am not going to go deep to Monad’s theory. My only point is to show how Scala Option Monad can help to deal with ‘coping with errors’ problem that is very common in many languages. Consider the following code:

class Document {
  def getActivePage: Page = ???
}
class Page {
  def getSelectedText: String = ???
}

The goal is to implement getSelectedTextLength method which returns the length of selected text on the active page, otherwise, it returns 0. The simple approach is to implement it as follows:

def getSelectedTextLength(doc: Document): Int = {
  if(doc != null) {
    val page = doc.getActivePage
    if(page != null){
      val text = page.getSelectedText
      if(text != null){
        text.length
      }
      else 0
    }
    else 0
  }
  else 0
}

Such implementation is OK, but it has nested indentation, aka pyramid of doom. There is another way to implement it:

def getSelectedTextLength(doc: Document): Int = {
  if(doc == null)
    return 0

  val page = doc.getActivePage
  if(page == null)
    return 0

  val text = page.getSelectedText
  if(text == null)
    return 0

  text.length
}

It looks flat and clean but has if (x == null) return 0 a pattern which appears many times. We can simplify it by using exceptions:

def getSelectedTextLength(doc: Document): Int = {
  try {
    doc.getActivePage.getSelectedText.length
  }
  catch {
    case _: NullPointerException => 0
    case e: Exception => throw e
  }
}

This version looks good but has some problem though. If NullPointerExceptionis thrown from getActivePage or getSelectedText it will be unintentionally handled by our code and by doing so, our code will hide the potential bug.

In Scala it can be solved by using Option Monad. Option monad wrappes value of any given type and have two specific implementations: None when a value does not exist (null) or Some for the existing value, plus it defines flatMap operation which allows composing operations sequence together.

trait Option[A] {
  def flatMap[B](f: A => Option[B]): Option[B]
}

case class None[A]() extends Option[A] {
  def flatMap[B](f: A => Option[B]): Option[B] = new None
}

case class Some[A](a: A) extends Option[A] {
  def flatMap[B](f: A => Option[B]): Option[B] = {
    f(a)
  }
}

So by using Option monad, we can reimplement the code as follows

class Document {
  def getActivePage: Option[Page] = ???
}
class Page {
  def getSelectedText: Option[String] = ???
}

def getSelectedTextLength(doc: Option[Document]): Int = {
  doc
    .flatMap(_.getActivePage)
    .flatMap(_.getSelectedText)
    .map(_.length).getOrElse(0)
}

Summary

Scala isn’t perfect, it comes with its own limitation and drawbacks. It has a steep learning curve because its principles are based on mathematical type theory which is hard to master. When not maintained carefully, the Scala source code is hard to read and understand. Some big tech companies like LinkedIn, Twitter, Yammer reported that they are abandoning or decreasing their dependency on Scala.

On the other hand, Scala is flexible, productive and comes with a pretty rich toolbox. Compatibility with Java allows Scala developers to enjoy of rich Java ecosystem of libraries, frameworks, and tools. All these facts make Scala my language of choice when it comes to developing Big Data pipelines or high load server backends.

Java and Scala: Why Should You Learn Scala?

Java and Scala: Why Should You Learn Scala?

Java is a general purpose object oriented language. Scala is less readable due to nested code. The process of compiling source code into byte code is slow. ... Scala treated everything as an instance of the class and it is more object oriented language as compare to Java.

There is admittedly some truth to the statement that “Scala is hard”, but the learning curve is well worth the investment. Some of the more complex features of the language (TuplesFunctionsMacros, to name a few) ultimately make it easier for the developer to write better code and increase performance by programming in Scala. Frankly, we are programmers, and if we’re not smart enough to learn a language that has some complexity, then we’re in the wrong business.

Scala is a type-safe JVM language that incorporates both object oriented and functional programming into an extremely concise, logical, and extraordinarily powerful language. Some may be surprised to know that Scala is not quite as new as they thought, having first been introduced in 2003. However, it is particularly within the past few years that Scala has begun to develop a significant following. Which begs the question of “Why Scala?”.

This article examines the advantages of Scala, especially versus Java (since Scala is written to run in the JVM). Scala is not the only attempt to create a “better Java”. Alternatives such as Kotlin and Ceylon have also gone down that path, but they made the fundamental decision to remain very close in syntax to the Java language itself, so as to minimize the learning curve. This may seem like a great idea, but it is ultimately somewhat self-defeating in that it forces you to stay within a number of those very same Java paradigms that were the reason for wanting to create a “better Java” in the first place.

In contrast, Scala was created specifically with the goal of being a better language, shedding those aspects of Java which it considered restrictive, overly tedious, or frustrating for the developer. As a result, there are indeed code distinctions and paradigm shifts that can make early learning of Scala programming a bit more difficult, but the result is a much cleaner and well organized language that is ultimately easier to use and increases productivity.

Java vs. Scala: Which is Really More Complex?

While the simplicity of the Java language has been part of its success, ironically, it has also contributed to its complexity. Sure, you can write nearly anything in Java, but the lines of code required to do so can be daunting. Programming in Scala, on the other hand, has a slightly more complex structure. But if you can write a slightly more complex single line of code that replaces 20 “simpler” lines of Java, which one is really more complex?

The truth is that Java is often just way too verbose. In Scala, the compiler is incredibly smart, so this avoids the developer needing to specify explicitly those things that the compiler can infer. Compare, for example, this simple “Hello World!” program in Java vs. Scala:

Hello World in Java:

public class HelloJava {
    public static void main(String[] args) {
        System.out.println("Hello World!");
    }
}

Hello World in Scala:

object HelloScala {
    def main(args: Array[String]): Unit = {
        println("Hello World!")
    }
}

While there’s not a huge distinction between the two languages here, Scala is less verbose even in this simple example.

For a more practical example, let’s take a look at creating a simple list of Strings:

Java:

List<String> list = new ArrayList<String>();
list.add("1");
list.add("2");
list.add("3");

Scala:

val list = List("1", "2", "3")

Certainly there are some tricks in Java to shorten the code a bit, but not in standard usage.

Now consider a case where we have a list of strings that are numbers, but we want to convert that list to a list of integers:

Java:

List<Integer> ints = new ArrayList<Integer>();
for (String s : list) {
    ints.add(Integer.parseInt(s));
}

Scala:

val ints = list.map(s => s.toInt)

Thanks to Scala’s functional properties, this conversion becomes extremely simple.

A Class Example: Java vs. Scala

Let’s take things a step further and compare standard bean / plain old Java object (POJO) creation in Java and Scala.

First, the Java version:

public class User {
    private String name;
    private List<Order> orders;
public User() {
    orders = new ArrayList&lt;Order&gt;();
}

public String getName() {
    return name;
}

public void setName(String name) {
    this.name = name;
}

public List&lt;Order&gt; getOrders() {
    return orders;
}

public void setOrders(List&lt;Order&gt; orders) {
    this.orders = orders;
}

}

public class Order {
private int id;
private List<Product> products;

public Order() {
    products = new ArrayList&lt;Product&gt;();
}

public int getId() {
    return id;
}

public void setId(int id) {
    this.id = id;
}

public List&lt;Product&gt; getProducts() {
    return products;
}

public void setProducts(List&lt;Product&gt; products) {
    this.products = products;
}

}

public class Product {
private int id;
private String category;

public int getId() {
    return id;
}

public void setId(int id) {
    this.id = id;
}

public String getCategory() {
    return category;
}

public void setCategory(String category) {
    this.category = category;
}

}

Phew. Lotta code.

Now the Scala version:

class User {
var name: String = _
var orders: List[Order] = Nil
}

class Order {
var id: Int = _
var products: List[Product] = Nil
}

class Product {
var id: Int = _
var category: String = _
}

Which language did we say was more complicated?!

Are We Being Fair?

If you’ve made it this far, and are a Java programmer, you may at this point be thinking that I’m making an unfair comparison of code. After all, there’s nothing stopping me from making public variables in Java and then getting rid of the getters and setters.

However, if you think back to the reasoning behind getters and setters in Java, it is specifically for future-proofing. That is, if you later need to add some logic to either the getting or setting of variables you would have to re-write those public variables to use methods instead (which is why the use of getters and setters to begin with is encouraged in Java). However, in Scala programming this isn’t the case. Because of the language design, the abstraction remains intact without needing getters and setters. Consider, for example, this modified User class in Scala that throws a NullPointerException if you try to set the name to null:

class User {
private var _name: String = _
var orders: List[Order] = Nil
def name = name
def name
=(name: String) = {
if (name == null) {
throw new NullPointerException("User.name cannot be null!")
}
_name = name
}

And you can still set the name like this:

user.name = "John Doe"

Note that this entirely removes the need to pre-configure method accessors.

Moreover, since Scala prefers immutability, I can write this in Scala even more concisely with case classes:

case class User(name: String, orders: List[Order])
case class Order(id: Int, products: List[Product])
case class Product(id: Int, category: String)

Pretty crazy how much less code I have to write.

Taking the Example a bit Further

Now consider a scenario with the above classes where I want to add a nifty little method in the User class that returns a list of all Products that the User has ordered:

In the verbose world of Java:

public List<Product> getProducts() {
List<Product> products = new ArrayList<Product>();
for (Order order : orders) {
products.addAll(order.getProducts());
}
return products;
}

Fortunately, java.util.List has an addAll method, or getProducts() would have been an even longer in Java.

In Scala, on the other hand, all we need is:

def products = orders.flatMap(o => o.products)

You can see just how much smaller the Scala language implementation is. Yes, it may seem more complex to the Scala newbie, but once you actually fully understand the concepts behind it, the Scala code will look far more simplistic than the Java code.

Let’s get even a bit more complicated here. What if we want to only get the Products within a specific Category?

In this case we aren’t able to take advantage of the addAll method in java.util.List, so things get uglier in Java:

public List<Product> getProductsByCategory(String category) {
List<Product> products = new ArrayList<Product>();
for (Order order : orders) {
for (Product product : order.getProducts()) {
if (category.equals(product.getCategory())) {
products.add(product);
}
}
}
return products;
}

In Scala, however, the code remains fairly straightforward. We simply use flatMap to combine the products lists from each Order flattened in a single list, then we filter to only include the ones that match the category:

def productsByCategory(category: String) = orders.flatMap(o => o.products).filter(p => p.category == category)
Dynamic vs. static

There has certainly been no shortage of new languages over the past few years, but whereas nearly all the others that have recently emerged are dynamic, Scala is statically-typed.

As a professional developer – though I know and use many dynamic languages – it is my opinion that compile-time checks are incredibly important to write solid code. In a dynamic language, you can never be sure that your code is sufficiently bug-free and robust until you actually run it in a wide range of scenarios. This can lead to potentially serious defects in code that never get realized until the code is in production.

Wrap-up

Hopefully, this article stacks up Java vs. Scala enough to give you a preliminary sense of the power and capabilities of Scala and whets your appetite for learning the language. Not only is it a great language that can make programming less tedious and more enjoyable, but it’s also being used by some of the largest companies in the world (LinkedIn, Twitter, FourSquare, The Guardian, to name just a few).

The popularity and usage of Scala is rapidly on the rise, as evidenced by the ever-increasing number of open positions for Scala developers. If you haven’t already done so, now would be a good time to begin riding the wave and stop asking “Why learn Scala?”

Thanks For Visiting, Keep Visiting. If you liked this post, share it with all of your programming buddies!

Further reading

A Scala tutorial for Java developers

The Scala Chronicles: The Beginning. 


Originally published on toptal.com

Focus on Your Data Structures With Scala Lenses

Focus on Your Data Structures With Scala Lenses

Focus on Your Data Structures With Scala Lenses. With new programming techniques come new problems and new patterns to solve them

With new programming techniques come new problems and new patterns to solve them.

In functional programming, immutability is a must. As a consequence, whenever it is needed to modify the content of a data structure, a new instance with updated values is created. Depending on how complex the data structure is, creating a copy may be a verbose task.

To simplify the process, a set of functions, generically called Optics, have been designed to access/modify parts of a whole in an easy way. Those functions must obey some laws that make their behavior predictable and intuitive (for instance, if we modify a value and then read it back, we should obtain the modified value).

Monocle Examples

To illustrate the use of Monocle, let's start by creating a simple domain model:

import monocle.macros.Lenses
sealed trait RoomTariff
case class NonRefundable(fee: BigDecimal) extends RoomTariff
case class Flexible(fee: BigDecimal) extends RoomTariff

@Lenses("") case class Hotel(name: String, address: String, rating: Int, rooms: List[Room], facilities: Map[String, List[String]])
@Lenses("
") case class Room(name: String, boardType: Option[String], price: Price, roomTariff: RoomTariff)
@Lenses("_") case class Price(amount: BigDecimal, currency: String)

The annotation @Lenses generates automatically a lens for each attribute of the case class.

Let's create an imaginary hotel:

val rooms = List(
Room("Double", Some("Half Board"), Price(10, "USD"), NonRefundable(1)),
Room("Twin", None, Price(20, "USD"), Flexible(0)) ,
Room("Executive", None, Price(200, "USD"), Flexible(0))
)
val facilities = Map("business" -> List("conference room"))
val hotel = Hotel("Hotel Paradise", "100 High Street", 5, rooms, facilities)

And now, the fun part:

Room changes based on the room position in the List 

test("double price of even rooms") {

val updatedHotel = (_rooms composeTraversal filterIndex{i: Int =&gt; i/2*2 == i} composeLens _price composeLens _amount modify(_ * 2)) (hotel)

assert(updatedHotel.rooms(0).price.amount == hotel.rooms(0).price.amount * 2)
assert(updatedHotel.rooms(1).price.amount == hotel.rooms(1).price.amount)
assert(updatedHotel.rooms(2).price.amount == hotel.rooms(2).price.amount * 2)

}

test("set price of 2nd room") {

val newValue = 12
val roomToUpdate = 1

assert(hotel.rooms(roomToUpdate).price.amount != newValue)


val updatedHotel = (_rooms composeOptional index(roomToUpdate) composeLens _price composeLens _amount set newValue)(hotel)
val updatedRoomList = (index[List[Room], Int, Room](roomToUpdate) composeLens _price composeLens _amount set newValue)(hotel.rooms)
assert(updatedHotel.rooms(roomToUpdate).price.amount == newValue)
assert(updatedRoomList(roomToUpdate).price.amount == newValue)

}

Modifying a non-existing room

test("no changes are made when attempting to modify a non-existing room") {

val newValue = 12
val roomToUpdate = 3

assert(hotel.rooms.length == 3)

val updatedHotel = (_rooms composeOptional index(roomToUpdate) composeLens _price composeLens _amount set newValue)(hotel)

assert(hotel == updatedHotel)

}

test("hotel 'disappears' when attempting to modify a non-existing room") {

val newValue = 12
val roomToUpdate = 3

assert(hotel.rooms.length == 3)

val updatedHotel = (_rooms composeOptional index(roomToUpdate) composeLens _price composeLens _amount setOption newValue)(hotel)

assert(updatedHotel.isEmpty)

}

Changing an optional value

test("set a value inside an Option") {

val newValue = "New Board Type"
val roomToUpdate = 0

assert(!hotel.rooms(roomToUpdate).boardType.contains(newValue))

val updatedHotel = (_rooms composeOptional index(roomToUpdate) composeLens _boardType composeOptional some.asOptional set newValue)(hotel)

assert(updatedHotel.rooms(roomToUpdate).boardType.contains(newValue))

}

test("no changes are made when attempting to modify an empty Option") {

val newValue = "New Board Type"
val roomToUpdate = 1

assert(hotel.rooms(roomToUpdate).boardType.isEmpty)

val updatedHotel = (_rooms composeOptional index(roomToUpdate) composeLens _boardType composeOptional some.asOptional set newValue)(hotel)

assert(updatedHotel.rooms(roomToUpdate).boardType.isEmpty)

}

test("hotel 'disappears' when attempting to modify an empty Option") {

val newValue = "New Board Type"
val roomToUpdate = 1

assert(hotel.rooms(roomToUpdate).boardType.isEmpty)

val updatedHotel = (_rooms composeOptional index(roomToUpdate) composeLens _boardType composeOptional some.asOptional setOption newValue)(hotel)

assert(updatedHotel.isEmpty)

}

Changes with an applicative function

test("divide prices by 10"){

assert(hotel.rooms(0).price.amount == 10)
assert(hotel.rooms(1).price.amount == 20)

val updatedHotel = (_rooms composeTraversal each composeLens _price composeLens _amount modify(_ / 10))(hotel)

assert(updatedHotel.rooms(0).price.amount == 1)
assert(updatedHotel.rooms(1).price.amount == 2)

}

test("divide prices by 0"){

assert(hotel.rooms(0).price.amount == 10)
assert(hotel.rooms(1).price.amount == 20)

val updatedHotel = (_rooms composeTraversal each composeLens _price composeLens _amount).modifyF[Option](y =&gt; Try{y / 0}.toOption)(hotel)

assert(updatedHotel.isEmpty)

}

Modifying the number of rooms

test("append a room"){

assert(hotel.rooms.length == 3)

val newRoom = Room("Triple", None, Price(1, "USD"), Flexible(0))

val updatedHotel = (_rooms set _snoc(hotel.rooms, newRoom))(hotel)

assert(updatedHotel.rooms.length == 4)
assert(updatedHotel.rooms(3) == newRoom)

}

test("prepend a room"){

assert(hotel.rooms.length == 3)

val newRoom = Room("Triple", None, Price(1, "USD"), Flexible(0))

val updatedHotel = (_rooms set _cons(newRoom, hotel.rooms))(hotel)

assert(updatedHotel.rooms.length == 4)
assert(updatedHotel.rooms(0) == newRoom)

}

Using prisms to modify the room tariff

test("set prices of Flexible rooms"){

val prism = Prism.partial[RoomTariff, BigDecimal]{case Flexible(x) =&gt; x}(Flexible)

val newValue = 100

assert(hotel.rooms(0).roomTariff == NonRefundable(1))
assert(hotel.rooms(1).roomTariff == Flexible(0))
assert(hotel.rooms(2).roomTariff == Flexible(0))

val updatedHotel = (_rooms composeTraversal each composeLens _roomTariff composePrism prism set newValue)(hotel)

assert(hotel.rooms(0).roomTariff == updatedHotel.rooms(0).roomTariff)
assert(updatedHotel.rooms(1).roomTariff == Flexible(newValue))
assert(updatedHotel.rooms(2).roomTariff == Flexible(newValue))

}

Manipulating a Map

test("modifying business facilities") {

val updatedHotel = (_facilities composeLens at("business") set Some(List("")))(hotel)

assert(updatedHotel.facilities("business") == List(""))

}

test("removing business facilities") {

val updatedHotel = (_facilities composeLens at("business") set None)(hotel)
val updatedFacilities = remove("business")(hotel.facilities)

assert(updatedHotel.facilities.get("business").isEmpty)
assert(updatedFacilities.get("business").isEmpty)

}

test("adding entertainment facilities") {

val updatedHotel = (_facilities composeLens at("entertainment") set  Some(List("satellite tv", "internet")))(hotel)

assert(updatedHotel.facilities("entertainment") == List("satellite tv", "internet"))

}

Folding over the room list

test("folding over room prices to add them up") {

assert(hotel.rooms(0).price.amount == 10)
assert(hotel.rooms(1).price.amount == 20)
assert(hotel.rooms(2).price.amount == 200)

assert((_rooms composeFold Fold.fromFoldable[List, Room] foldMap(_.price.amount))(hotel) == 230)

}

Modifying rooms that meet specific criteria

val unsafePrism = UnsafeSelect.unsafeSelect[Room](_.name == "Double")
test("double price of Double rooms using unsafe operation") {

val updatedHotel = (_rooms composeTraversal each composePrism unsafePrism composeLens _price composeLens _amount modify (_ * 2)) (hotel)

assert(hotel.rooms.filter(_.name == "Double").map(_.price.amount*2) == updatedHotel.rooms.filter(_.name == "Double").map(_.price.amount))

}

This last example makes use of an unsafe prism (it is unsafe because does not comply with all Prism laws). Let's verify this statement by testing the laws:

val roomGen: Gen[Room] = for {
name <- Gen.oneOf("Double", "Twin", "Executive")
board <- Gen.option(Gen.alphaStr)
price <- for{
price <- Gen.posNum[Double]
currency <- Gen.oneOf("USD", "GBP", "EUR")
} yield Price(price, currency)
tariff <- Gen.oneOf(Gen.posNum[Double].map(NonRefundable()), Gen.posNum[Double].map(Flexible()))
} yield Room(name, board, price, tariff)

implicit val roomArb: Arbitrary[Room] = Arbitrary(roomGen)

implicit val arbAA: Arbitrary[Room => Room] = Arbitrary{
for{
room <- roomGen
} yield (_: Room) => room
}

checkAll("unsafe prism", PrismTests(unsafePrism))

When running the above test, the following tests fail:

  • Prism.compose modify
  • Prism.round trip another way

So, what is wrong? Let's check the law "round trip other way." Here's its definition on PrismLaws:

def roundTripOtherWay(a: A): IsEq[Option[A]] =
prism.getOption(prism.reverseGet(a)) <==> Some(a)

And this is how the law is broken:

val a = Room(Twin,None,Price(1.0,USD),Flexible(1.0))
val b = unsafePrism.reverseGet(a) = Room(Twin,None,Price(1.0,USD),Flexible(1.0))
val c = unsafePrism.getOption(b) = None

None != Some(a)

So, our unsafePrism is unsafe when used to make changes on the attribute included in the predicate to create the prism.

Thanks for reading. If you liked this post, share it with all of your programming buddies!

Further reading

The Scala Chronicles: The Beginning. 

PWA starter kit: build fast, scalable, modern apps with Web Components

A Scala tutorial for Java developers

12 tips for writing clean and scalable JavaScript

Python+MongoDB = Rapid & scalable app development



This post was originally published here