{{announcement.body}}
{{announcement.title}}

# Focus on Your Data Structures With Scala Lenses

DZone 's Guide to

# Focus on Your Data Structures With Scala Lenses

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

· Java Zone ·
Free Resource

Comment (0)

Save
{{ articles[0].views | formatCount}} Views

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).

This post presents some examples of how to use an Optics library in Scala called Monocle.

### 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 => 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 => 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) => 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)

}

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

}

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.

All the above examples can be found on this repo.

Topics:
java ,scala ,lenses ,tutorial ,data structures ,monocle

Comment (0)

Save
{{ articles[0].views | formatCount}} Views

Published at DZone with permission of Francisco Alvarez , DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.