Question

At the moment, I have a WorkLog type, with a start and end date. I want to also add a duration lens, which will be derived from the start and end dates. It should either be read only, or change the end date if its value is changed (I would like to know how to implement both version, even though I will only use one).

Here is my code. Basically, if you can implement the workLogDurationRO and workLogDurationRW functions to get all the tests in main passing, that would answer my question.

{-# LANGUAGE TemplateHaskell #-}
module Main where
import Control.Lens

-- Keep times simple for this example
newtype TimeStamp = TimeStamp Int deriving (Show, Eq)
newtype TimeDifference = TimeDifference Int deriving (Show, Eq)

(-.-) :: TimeStamp -> TimeStamp -> TimeDifference
(TimeStamp a) -.- (TimeStamp b) = TimeDifference (a - b)

data WorkLog = WorkLog {
  _workLogDescription :: String
  , _workLogStartTime :: TimeStamp
  , _workLogEndTime :: TimeStamp
  }

makeLenses ''WorkLog

-- | Just return the difference between the start and end time
workLogDurationRO :: Getter WorkLog TimeDifference
workLogDurationRO = error "TODO write me!"

-- | Like the read only version, but when used with a setter,
-- change the end date.
workLogDurationRW :: Lens' WorkLog TimeDifference
workLogDurationRW = error "TODO write me!"

ensure :: String -> Bool -> IO ()
ensure _ True = putStrLn "Test Passed"
ensure msg False = putStrLn $ "Test Failed: " ++ msg

main :: IO ()
main = do
  let testWorkLog = WorkLog "Work 1" (TimeStamp 40) (TimeStamp 100)
  ensure "read only lens gets correct duration" $ 
     testWorkLog^.workLogDurationRO == TimeDifference 60
  ensure "read+write lens gets correct duration" $ 
     testWorkLog^.workLogDurationRW == TimeDifference 60
  let newWorkLog = testWorkLog & workLogDurationRW .~ TimeDifference 5
  ensure "writeable lens changes end time" $ 
     newWorkLog^.workLogEndTime == TimeStamp 45
Was it helpful?

Solution

You can write the Getter using to (you could give -.- lower precedence to get rid of the parentheses):

workLogDurationRO = to $ \wl -> (wl^.workLogEndTime) -.- (wl^.workLogStartTime)

But as the lens wiki says, you're probably better off with a normal function that computes the time difference, which you can then use with to when you need it as a lens.

You can build the Lens' from a getter (same as above) and a setter:

workLogDurationRW = lens get set
  where
    get :: WorkLog -> TimeDifference
    get wl = (wl^.workLogEndTime) -.- (wl^.workLogStartTime)

    set :: WorkLog -> TimeDifference -> WorkLog
    set wl timeDiff = wl & workLogEndTime .~ (wl^.workLogStartTime) +.+ timeDiff
      where
        TimeStamp a +.+ TimeDifference b = TimeStamp (a + b)

OTHER TIPS

workLogDurationRO :: Getter WorkLog TimeDifference
workLogDurationRO f w@(WorkLog d s e) = fmap (const w) (f $ e -.- s)


workLogDurationRW :: Lens' WorkLog TimeDifference
workLogDurationRW f (WorkLog d s@(TimeStamp y) e) = 
   fmap (\(TimeDifference x) -> WorkLog d s (TimeStamp $ y + x)) (f $ e -.- s)
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top