Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

exercise lense-person #157

Merged
merged 4 commits into from
Sep 27, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion config.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,8 @@
"parallel-letter-frequency",
"acronym",
"zipper",
"forth"
"forth",
"lens-person"
],
"exercises": [
{
Expand Down Expand Up @@ -445,6 +446,12 @@
"slug": "forth",
"difficulty": 1,
"topics": [
]
},
{
"slug": "lens-person",
"difficulty": 1,
"topics": [
]
}
],
Expand Down
19 changes: 19 additions & 0 deletions exercises/lens-person/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Lens Person

Use lenses to manipulate (get, set, modify) immutable objects.

Updating fields of nested (immutable) objects is kind of annoying in Scala. One solution
is to use [lenses](https://wiki.haskell.org/Lens).
Implement several class attribute accessing functions using lenses, you may use any library you want. One good choice could be [Monocle](https://julien-truffaut.github.io/Monocle/].
The test suite also allows you to avoid lenses alltogether 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](http://help.exercism.io/getting-started-with-scala.html).
11 changes: 11 additions & 0 deletions exercises/lens-person/build.sbt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
scalaVersion := "2.11.8"

libraryDependencies += "org.scalatest" % "scalatest_2.11" % "2.2.5" % "test"

libraryDependencies ++= Seq(
"com.github.julien-truffaut" % "monocle-core_2.11" % "1.2.2",
"com.github.julien-truffaut" % "monocle-macro_2.11" % "1.2.2"
)

libraryDependencies += "org.scalaz" %% "scalaz-core" % "7.2.6"

151 changes: 151 additions & 0 deletions exercises/lens-person/example.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import java.time.LocalDate
import LensPerson._

trait ToBeImplemented {

// Implement these.

val bornStreet: Born => String

val setCurrentStreet: String => Person => Person

val setBirthMonth: Int => Person => Person

// Transform both birth and current street names.
val renameStreets: (String => String) => Person => Person
}

object LensPerson extends ToBeImplemented with MonocleSolution /* with ScalazSolution */ {

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

trait MonocleSolution extends ToBeImplemented {
import monocle.PIso
import monocle.macros.GenLens

// Person Lenses
val personLens = GenLens[Person]
val name = personLens(_._name)
val born = personLens(_._born)
val address = personLens(_._address)

// Name Lenses
val nameLens = GenLens[Name]
val foreNames = nameLens(_._foreNames)
val surName = nameLens(_._surName)

// Born Lenses
val bornLens = GenLens[Born]
val bornAt = bornLens(_._bornAt)
val bornOn = bornLens(_._bornOn)

// Address Lenses
val street = GenLens[Address](_._street)

// Gregorian Lenses
val gregorianLens = GenLens[Gregorian]
val dayOfMonth = gregorianLens(_._dayOfMonth)
val month = gregorianLens(_._month)
val year = gregorianLens(_._year)

val bornStreet: Born => String =
bornAt ^|-> street get

val setCurrentStreet: String => Person => Person =
address ^|-> street set

val isoEpocheDayGregorian: PIso[EpochDay, EpochDay, Gregorian, Gregorian] = {
def epochDayToGregorian(ed: EpochDay): Gregorian = {
val ld = LocalDate.ofEpochDay(ed)
Gregorian(ld.getYear, ld.getMonth.getValue, ld.getDayOfMonth)
}
def gregorianToEpochDay(g: Gregorian): EpochDay =
LocalDate.of(g._year, g._month, g._dayOfMonth).toEpochDay

PIso(epochDayToGregorian _)(gregorianToEpochDay _)
}

val setBirthMonth: Int => Person => Person =
born ^|-> bornOn ^<-> isoEpocheDayGregorian ^|-> month set

val renameStreets: (String => String) => Person => Person =
f => ((born ^|-> bornAt ^|-> street modify f) .compose (address ^|-> street modify f))
}

trait ScalazSolution extends ToBeImplemented {
import scalaz.Lens

// Person Lenses
val name = Lens.lensu[Person, Name](
(person, name) => person.copy(_name = name), (_._name))
val born = Lens.lensu[Person, Born](
(person, born) => person.copy(_born = born), (_._born))
val address = Lens.lensu[Person, Address](
(person, address) => person.copy(_address = address), (_._address))

// Name Lenses
val foreNames = Lens.lensu[Name, String](
(name, foreNames) => name.copy(_foreNames = foreNames), (_._foreNames))
val surName = Lens.lensu[Name, String](
(name, surName) => name.copy(_surName = surName), (_._surName))

// Born Lenses
val bornAt = Lens.lensu[Born, Address](
(born, address) => born.copy(_bornAt = address), (_._bornAt))
val bornOn = Lens.lensu[Born, EpochDay](
(born, epochDay) => born.copy(_bornOn = epochDay), (_._bornOn))

// Address Lenses
val street = Lens.lensu[Address, String](
(address, street) => address.copy(_street = street), (_._street))

// Gregorian Lenses
val dayOfMonth = Lens.lensu[Gregorian, Int](
(gregorian, dayOfMonth) => gregorian.copy(_dayOfMonth = dayOfMonth), (_._dayOfMonth))
val month = Lens.lensu[Gregorian, Int](
(gregorian, month) => gregorian.copy(_month = month), (_._month))
val year = Lens.lensu[Gregorian, Int](
(gregorian, year) => gregorian.copy(_year = year), (_._year))


val bornStreet: Born => String =
bornAt >=> street get

val setCurrentStreet: String => Person => Person =
s => address >=> street set(_, s)

def epochDayToGregorian(ed: EpochDay): Gregorian = {
val ld = LocalDate.ofEpochDay(ed)
Gregorian(ld.getYear, ld.getMonth.getValue, ld.getDayOfMonth)
}
def gregorianToEpochDay(g: Gregorian): EpochDay =
LocalDate.of(g._year, g._month, g._dayOfMonth).toEpochDay

val setBirthMonth: Int => Person => Person =
m => born >=> bornOn =>= setMonth(m)

val setMonth: Int => EpochDay => EpochDay =
newMonth => oldEpochDay => {
val Gregorian(year, _, day) = epochDayToGregorian(oldEpochDay)
val newGregorian = Gregorian(year, newMonth, day)
gregorianToEpochDay(newGregorian)
}

val renameStreets: (String => String) => Person => Person =
f => ((born >=> bornAt >=> street =>= f) .compose (address >=> street =>= f))
}
30 changes: 30 additions & 0 deletions exercises/lens-person/src/main/scala/LensPerson.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import java.time.LocalDate

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.

val bornStreet: Born => String = ???

val setCurrentStreet: String => Person => Person = ???

val setBirthMonth: Int => Person => Person = ???

// Transform both birth and current street names.
val renameStreets: (String => String) => Person => Person = ???
}
50 changes: 50 additions & 0 deletions exercises/lens-person/src/test/scala/LensPersonTest.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import org.scalatest.{FunSuite, Matchers}
import java.time.LocalDate
import LensPerson._

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")
}
}