fit-0.5: src/Fit/Messages/Lens.hs
-- | Some basic lenses for the Messages API. These are compatible with both lens and lens-family.
-- This package doesn't provide any lens combinators like @^.@ or @^..@, so you'll need to use
-- ones from a lens package.
--
-- For example, the following code gets the values of the 'speed' fields
-- from all of the 'record' messages in the file:
--
-- @
-- Right fit <- readFileMessages "file.fit"
-- let speeds = fit ^.. message 20 . field 6 . int
-- @
module Fit.Messages.Lens (
-- * Messages
messages,
message,
messageNumber,
-- * Fields
fields,
field,
fieldNumber,
fieldValue,
-- * Values
-- $values
int,
real,
text,
byte,
ints,
reals,
bytestring
) where
import Fit.Messages
import Control.Applicative ((<$>), pure, Applicative)
import Data.ByteString (ByteString)
import Data.Functor.Contravariant (Contravariant, contramap)
import qualified Data.IntMap as Map (filterWithKey)
import Data.Sequence (Seq)
import qualified Data.Sequence as S (filter)
import Data.Text (Text)
import Data.Traversable (traverse)
import Data.Word (Word8)
-- Helper for building Folds
coerce :: (Contravariant f, Applicative f) => f a -> f b
coerce = contramap (const ()) . fmap (const ())
-- | Traverse all the messages in a @Messages@
--
-- @messages :: Traversal' Messages Message@
messages :: Applicative f => (Message -> f Message) -> Messages -> f Messages
messages f ms = Messages <$> traverse f (_messages ms)
{-# INLINE messages #-}
-- | A Fold over the messages with the given message number
--
-- @message :: Int -> Fold Messages Message@
message :: (Contravariant f, Applicative f) => Int -> (Message -> f Message) -> Messages -> f Messages
message msgNum f ms = coerce (traverse f targets)
where targets = S.filter ((== msgNum) . _mNumber) (_messages ms)
{-# INLINE message #-}
-- | Lens on the message number from a @Message@
--
-- @messageNumber :: Lens' Message Int@
messageNumber :: Functor f => (Int -> f Int) -> Message -> f Message
messageNumber f m = (\n -> m { _mNumber = n }) <$> f (_mNumber m)
{-# INLINE messageNumber #-}
-- | Traverse all the fields in a @Message@
--
-- @fields :: Traversal' Message Field@
fields :: Applicative f => (Field -> f Field) -> Message -> f Message
fields f (Message n flds) = Message n <$> traverse f flds
{-# INLINE fields #-}
-- | A Fold over the fields in a @Message@ with the given field number
--
-- @field :: Int -> Fold Message Field@
field :: (Contravariant f, Applicative f) => Int -> (Field -> f Field) -> Message -> f Message
field n f msg = coerce (traverse f targetFields)
where targetFields = Map.filterWithKey (\k _ -> k == n) (_mFields msg)
{-# INLINE field #-}
-- | Lens on the field number from a @Field@
--
-- @fieldNumber :: Lens Field Int@
fieldNumber :: Functor f => (Int -> f Int) -> Field -> f Field
fieldNumber f fld = (\n -> fld { _fNumber = n }) <$> f (_fNumber fld)
{-# INLINE fieldNumber #-}
-- | Lens on the @Value@ from a @Field@
--
-- @fieldValue :: Lens Field Value@
fieldValue :: Functor f => (Value -> f Value) -> Field -> f Field
fieldValue f fld = (\v -> fld { _fValue = v }) <$> f (_fValue fld)
{-# INLINE fieldValue #-}
-- $values
-- Generally when you're looking up the value for a particular field you'll know
-- the expected type ahead of time. If you know the field you're looking at holds
-- integers, then you can use @int@ to directly get an @Int@ instead of a
-- @Singleton (IntValue x)@.
--
-- These traversals are not prisms, because to reconstruct the @Field@ we need
-- the field number in addition to the wrapped value.
-- | Traverse the @Singleton@ and @IntValue@ constructors for a field value
--
-- @int :: Traversal' Field Int@
int :: Applicative f => (Int -> f Int) -> Field -> f Field
int f (Field n (Singleton (IntValue i))) = Field n . Singleton . IntValue <$> f i
int _ fld = pure fld
{-# INLINE int #-}
-- | Traverse the @Singleton@ and @RealValue@ constructors for a field value
--
-- @real :: Traversal' Field Double@
real :: Applicative f => (Double -> f Double) -> Field -> f Field
real f (Field n (Singleton (RealValue d))) = Field n . Singleton . RealValue <$> f d
real _ fld = pure fld
{-# INLINE real #-}
-- | Traverse the @Singleton@ and @TextValue@ constructors for a field value
--
-- @text :: Traversal' Field Text@
text :: Applicative f => (Text -> f Text) -> Field -> f Field
text f (Field n (Singleton (TextValue t))) = Field n . Singleton . TextValue <$> f t
text _ fld = pure fld
{-# INLINE text #-}
-- | Traverse the @Singleton@ and @ByteValue@ constructors for a field value
--
-- @byte :: Traversal' Field Word8@
byte :: Applicative f => (Word8 -> f Word8) -> Field -> f Field
byte f (Field n (Singleton (ByteValue b))) = Field n . Singleton . ByteValue <$> f b
byte _ fld = pure fld
{-# INLINE byte #-}
-- | Traverse the @Array@ and @IntArray@ constructors for a field value
--
-- @ints :: Traversal' Field (Seq Int)@
ints :: Applicative f => (Seq Int -> f (Seq Int)) -> Field -> f Field
ints f (Field n (Array (IntArray s))) = Field n . Array . IntArray <$> f s
ints _ fld = pure fld
{-# INLINE ints #-}
-- | Traverse the @Array@ and @RealArray@ constructors for a field value
--
-- @reals :: Traversal' Field (Seq Double)@
reals :: Applicative f => (Seq Double -> f (Seq Double)) -> Field -> f Field
reals f (Field n (Array (RealArray s))) = Field n . Array . RealArray <$> f s
reals _ fld = pure fld
{-# INLINE reals #-}
-- | Travese the @Array@ and @ByteArray@ constructors for a field value
--
-- @bytestring :: Traversal' Field ByteString@
bytestring :: Applicative f => (ByteString -> f ByteString) -> Field -> f Field
bytestring f (Field n (Array (ByteArray bs))) = Field n . Array . ByteArray <$> f bs
bytestring _ fld = pure fld
{-# INLINE bytestring #-}