问题
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
回答1:
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)
回答2:
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)
来源:https://stackoverflow.com/questions/21255541/how-do-you-write-a-complex-lens-that-depend-on-other-lenses-using-the-lens-libra