Use lenses to update nested records (specific to languages with immutable data).
Updating fields of nested records is kind of annoying in Haskell. One solution is to use lenses. Implement several record accessing functions using lenses, you may use any library you want. The test suite also allows you to avoid lenses altogether so you can experiment with different approaches.
The Scala exercises assume an SBT project scheme. The exercise solution source should be placed within the exercise directory/src/main/scala. The exercise unit tests can be found within the exercise directory/src/test/scala.
To run the tests simply run the command sbt test
in the exercise directory.
For more detailed info about the Scala track see the help page.
It's possible to submit an incomplete solution so you can see how others have completed the exercise.
import org.scalatest.{FunSuite, Matchers}
import java.time.LocalDate
import LensPerson._
/** @version created manually **/
class LensPersonTest extends FunSuite with Matchers {
val testPerson =
Person(
_name = Name(
_foreNames = "Jane Joanna",
_surName = "Doe"),
_born = Born(
_bornAt = Address(
_street = "Longway",
_houseNumber = 1024,
_place = "Springfield",
_country = "United States"),
_bornOn = toEpochDay(1984, 4, 12)),
_address = Address(
_street = "Shortlane",
_houseNumber = 2,
_place = "Fallmeadow",
_country = "Canada"))
def toEpochDay(year: Int, month: Int, dayOfMonth: Int) =
LocalDate.of(year, month, dayOfMonth).toEpochDay
test("bornStreet") {
bornStreet(testPerson._born) should be ("Longway")
}
test("setCurrentStreet") {
pending
(setCurrentStreet("Middleroad")(testPerson))._address._street should be ("Middleroad")
}
test("setBirthMonth") {
pending
setBirthMonth(9)(testPerson)._born._bornOn should be (toEpochDay(1984, 9, 12))
}
test("renameStreets birth") {
pending
renameStreets(_.toUpperCase)(testPerson)._born._bornAt._street should be ("LONGWAY")
}
test("renameStreets current") {
pending
renameStreets(_.toUpperCase)(testPerson)._address._street should be ("SHORTLANE")
}
}
import java.time.LocalDate
import monocle.macros.GenLens
import scala.language.postfixOps
object LensPerson {
case class Person(_name: Name, _born: Born, _address: Address)
case class Name(_foreNames: String /*Space separated*/ , _surName: String)
// Value of java.time.LocalDate.toEpochDay
type EpochDay = Long
case class Born(_bornAt: Address, _bornOn: EpochDay)
case class Address(_street: String, _houseNumber: Int,
_place: String /*Village / city*/ , _country: String)
// Valid values of Gregorian are those for which 'java.time.LocalDate.of'
// returns a valid LocalDate.
case class Gregorian(_year: Int, _month: Int, _dayOfMonth: Int)
// Implement these.
/*
// START of my solution is
lazy val bornStreet: Born => String = _._bornAt._street
lazy val setCurrentStreet: String => Person => Person =
street => person => person
.copy(_address = person._address
.copy(_street = street))
lazy val setBirthMonth: Int => Person => Person =
month => person => person
.copy(_born = person._born
.copy(_bornOn = LocalDate.ofEpochDay(person._born._bornOn).withMonth(month).toEpochDay))
// Transform both birth and current street names.
lazy val renameStreets: (String => String) => Person => Person =
fun => person => person
.copy(_born = person._born
.copy(_bornAt = person._born._bornAt
.copy(_street = fun(person._born._bornAt._street))),
_address = person._address
.copy(fun(person._address._street)))
// END of my solution is
*/
//solution borrowed from Wows. I like it because it shows how to use project dependency (monocle-core, monocle-macro)
private val addressLens = GenLens[Person](_._address)
private val bornLens = GenLens[Person](_._born)
private val bornAddressLens = GenLens[Born](_._bornAt)
private val streetLens = GenLens[Address](_._street)
private val birthDateLens = GenLens[Person](_._born._bornOn)
val bornStreet: Born => String = bornAddressLens composeLens streetLens get
val setCurrentStreet: String => Person => Person =
s => p => (addressLens composeLens streetLens).modify(_ => s)(p)
val setBirthMonth: Int => Person => Person = month => p =>
birthDateLens.modify(day => {
val date = LocalDate.ofEpochDay(day)
LocalDate.of(date.getYear, month, date.getDayOfMonth).toEpochDay
})(p)
// Transform both birth and current street names.
val renameStreets: (String => String) => Person => Person = f =>
p => {
val person = (bornLens composeLens bornAddressLens composeLens streetLens).modify(s => f(s))(p)
(addressLens composeLens streetLens).modify(s => f(s))(person)
}
}
A huge amount can be learned from reading other people’s code. This is why we wanted to give exercism users the option of making their solutions public.
Here are some questions to help you reflect on this solution and learn the most from it.
Level up your programming skills with 3,449 exercises across 52 languages, and insightful discussion with our volunteer team of welcoming mentors. Exercism is 100% free forever.
Sign up Learn More
Community comments