Skip to content

Commit 0773fc5

Browse files
committed
Add lens-person exercise
This is a relatively easy exercise to play around with basic lenses.
1 parent b7177df commit 0773fc5

File tree

4 files changed

+169
-0
lines changed

4 files changed

+169
-0
lines changed

config.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@
7272
"go-counting",
7373
"zipper",
7474
"forth",
75+
"lens-person",
7576
"pov"
7677
],
7778
"deprecated": [

lens-person/Person.hs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
module Person where
2+
3+
import Data.Time.Calendar
4+
5+
data Person = Person {
6+
_name :: Name,
7+
_born :: Born,
8+
_address :: Address
9+
}
10+
11+
data Name = Name {
12+
_foreNames :: String, -- Space separated
13+
_surName :: String
14+
}
15+
16+
data Born = Born {
17+
_bornAt :: Address,
18+
_bornOn :: Day
19+
}
20+
21+
data Address = Address {
22+
_street :: String,
23+
_houseNumber :: Int,
24+
_place :: String, -- Village / city
25+
_country :: String
26+
}
27+
28+
-- Valid values of Gregorian are those for which 'Data.Time.Calendar.fromGregorianValid'
29+
-- returns Just.
30+
data Gregorian = Gregorian {
31+
_year :: Integer,
32+
_month :: Int,
33+
_day :: Int
34+
}
35+
36+
-- Implement these.
37+
38+
bornStreet :: Born -> String
39+
40+
setCurrentStreet :: String -> Person -> Person
41+
42+
setBirthMonth :: Int -> Person -> Person
43+
44+
-- | Transform both birth and current street names.
45+
renameStreets :: (String -> String) -> Person -> Person

lens-person/example.hs

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
{-# LANGUAGE TemplateHaskell #-}
2+
3+
module Person where
4+
5+
import Control.Lens
6+
import Data.Time.Calendar
7+
8+
data Person = Person {
9+
_name :: Name,
10+
_born :: Born,
11+
_address :: Address
12+
}
13+
14+
data Name = Name {
15+
_foreNames :: String, -- Space separated
16+
_surName :: String
17+
}
18+
19+
data Born = Born {
20+
_bornAt :: Address,
21+
_bornOn :: Day
22+
}
23+
24+
data Address = Address {
25+
_street :: String,
26+
_houseNumber :: Int,
27+
_place :: String, -- Village / city
28+
_country :: String
29+
}
30+
31+
-- Valid values of Gregorian are those for which 'Data.Time.Calendar.fromGregorianValid'
32+
-- returns Just.
33+
data Gregorian = Gregorian {
34+
_year :: Integer,
35+
_month :: Int,
36+
_day :: Int
37+
}
38+
39+
makeLenses ''Person
40+
makeLenses ''Name
41+
makeLenses ''Born
42+
makeLenses ''Address
43+
makeLenses ''Gregorian
44+
45+
gregorianDay :: Iso' Gregorian Day
46+
gregorianDay = iso toDay fromDay
47+
where
48+
toDay (Gregorian y m d) = fromGregorian y m d
49+
fromDay d' = let (y, m, d) = toGregorian d' in Gregorian y m d
50+
51+
bornStreet :: Born -> String
52+
bornStreet = view (bornAt . street)
53+
54+
setCurrentStreet :: String -> Person -> Person
55+
setCurrentStreet = set (address . street)
56+
57+
setBirthMonth :: Int -> Person -> Person
58+
setBirthMonth = set (born . bornOn . from gregorianDay . month)
59+
60+
renameStreets :: (String -> String) -> Person -> Person
61+
renameStreets f = over birthStreet f . over currentStreet f
62+
where
63+
birthStreet = born . bornAt . street
64+
currentStreet = address . street

lens-person/lens-person_test.hs

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import Data.Char (toUpper)
2+
import Data.Time.Calendar (fromGregorian)
3+
import System.Exit (ExitCode (..), exitWith)
4+
import Test.HUnit (Assertion, Counts (..), Test (..),
5+
runTestTT, (@=?))
6+
7+
import Person (Address (..), Born (..),
8+
Name (..), Person (..), bornStreet,
9+
renameStreets, setBirthMonth,
10+
setCurrentStreet)
11+
12+
testCase :: String -> Assertion -> Test
13+
testCase label assertion = TestLabel label (TestCase assertion)
14+
15+
exitProperly :: IO Counts -> IO ()
16+
exitProperly m = do
17+
counts <- m
18+
exitWith $ if failures counts /= 0 || errors counts /= 0 then ExitFailure 1 else ExitSuccess
19+
20+
main :: IO ()
21+
main = exitProperly $ runTestTT $ TestList
22+
[ TestList personTests ]
23+
24+
testPerson :: Person
25+
testPerson = Person {
26+
_name = Name {
27+
_foreNames = "Jane Joanna",
28+
_surName = "Doe"
29+
},
30+
_born = Born {
31+
_bornAt = Address {
32+
_street = "Longway",
33+
_houseNumber = 1024,
34+
_place = "Springfield",
35+
_country = "United States"
36+
},
37+
_bornOn = fromGregorian 1984 4 12
38+
},
39+
_address = Address {
40+
_street = "Shortlane",
41+
_houseNumber = 2,
42+
_place = "Fallmeadow",
43+
_country = "Canada"
44+
}
45+
}
46+
47+
personTests :: [Test]
48+
personTests =
49+
[ testCase "bornStreet" $
50+
"Longway" @=? bornStreet (_born testPerson),
51+
testCase "setCurrentStreet" $
52+
"Middleroad" @=? (_street . _address) (setCurrentStreet "Middleroad" testPerson),
53+
testCase "setBirthMonth" $
54+
fromGregorian 1984 9 12 @=? (_bornOn . _born) (setBirthMonth 9 testPerson),
55+
testCase "renameStreets birth" $
56+
"LONGWAY" @=? (_street . _bornAt . _born) (renameStreets (map toUpper) testPerson),
57+
testCase "renameStreets current" $
58+
"SHORTLANE" @=? (_street . _address) (renameStreets (map toUpper) testPerson)
59+
]

0 commit comments

Comments
 (0)