Как написать сложный объектив, который зависит от других объективов, используя библиотеку объективов?

На данный момент у меня есть тип WorkLog с датой начала и окончания. Я хочу также добавить линзу продолжительности, которая будет выводиться из дат начала и окончания. Он должен быть либо только для чтения, либо изменить дату окончания, если его значение изменилось (я хотел бы знать, как реализовать обе версии, хотя я буду использовать только одну).

Вот мой код. По сути, если вы можете реализовать функции workLogDurationRO и workLogDurationRW, чтобы получить все тесты в основном проходе, это ответит на мой вопрос.

{-# 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

person David Miani    schedule 21.01.2014    source источник


Ответы (1)


Вы можете написать Getter, используя to (вы можете дать -.- более низкий приоритет, чтобы избавиться от скобок):

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

Но, как говорится в вики-странице по объективам, вам, вероятно, лучше использовать обычную функцию, которая вычисляет разницу во времени, которую затем можно использовать с to, когда он вам понадобится в качестве объектива.

Вы можете построить Lens' из получателя (как указано выше) и установщика:

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)
person raymonad    schedule 21.01.2014
comment
Спасибо, не додумался использовать функцию линзы (хотя задним числом это очевидно). Я все еще пытаюсь разобраться в этой библиотеке :) - person David Miani; 21.01.2014