How do you write a complex lens that depend on other lenses using the lens library?

ぃ、小莉子 提交于 2019-12-10 18:19:41

问题


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

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!