content: {-# LANGUAGE FlexibleInstances     #-}
  {-# LANGUAGE MultiParamTypeClasses #-}
  {-# LANGUAGE Safe                  #-}
  {-# LANGUAGE TypeFamilies          #-}
  -- The following warning is disabled due to a necessary instance of Projectable
  -- defined in this module.
  {-# OPTIONS_GHC -fno-warn-orphans #-}
  
  -- | Combinators to deal with streams carrying structs.
  --
  -- We support two kinds of operations on structs: reading the fields of structs
  -- and modifying the fields of structs.
  --
  -- To obtain the values of field @x@ of a struct @s@, you can just write:
  --
  -- @
  -- expr = s # x
  -- @
  --
  -- If you want to update it, use instead a double hash to refer to the field.
  -- You can either update the field:
  --
  -- @
  -- expr = s ## x =: 75
  -- @
  --
  -- To update it by applying a function to it, for example, the function that
  -- updates a stream by one unit, just do:
  --
  -- @
  -- expr = s ## x =$ (+1)
  -- @
  module Copilot.Language.Operators.Struct
    ( Projectable(..)
    , (#)
    , (##)
    ) where
  
  import Copilot.Core.Type
  import Copilot.Core.Operators
  import Copilot.Language.Operators.Projection
  import Copilot.Language.Stream               (Stream (..))
  
  import GHC.TypeLits (KnownSymbol)
  
  -- | Create a stream that carries a field of a struct in another stream.
  --
  -- This function implements a projection of a field of a struct over time. For
  -- example, if a struct of type @T@ has two fields, @t1@ of type @Int@ and @t2@
  -- of type @Word8@, and @s@ is a stream of type @Stream T@, then @s # t2@ has
  -- type @Stream Word8@ and contains the values of the @t2@ field of the structs
  -- in @s@ at any point in time.
  (#) :: (KnownSymbol f, Typed t, Typed s, Struct s)
        => Stream s -> (s -> Field f t) -> Stream t
  (#) s f = Op1 (GetField typeOf typeOf f) s
  
  -- | Pair a stream with a field accessor, without applying it to obtain the
  -- value of the field.
  --
  -- This function is needed to refer to a field accessor when the goal is to
  -- update the field value, not just to read it.
  (##) :: (KnownSymbol f, Typed t, Typed s, Struct s)
       => Stream s -> (s -> Field f t) -> Projection s (s -> Field f t) t
  (##) = ProjectionS
  
  -- | Update a stream of structs.
  
  -- This is an orphan instance; we suppress the warning that GHC would
  -- normally produce with a GHC option at the top.
  instance (KnownSymbol f, Typed s, Typed t, Struct s)
        => Projectable s (s -> Field f t) t
    where
  
    -- | A projection of a field of a stream of structs.
    data Projection s (s -> Field f t) t = ProjectionS (Stream s) (s -> Field f t)
  
    -- | Create a stream where the field of a struct has been updated with values
    -- from another stream.
    --
    -- For example, if a struct of type @T@ has two fields, @t1@ of type @Int32@
    -- and @t2@ of type @Word8@, and @s@ is a stream of type @Stream T@, and
    -- $sT1$ is a stream of type @Int32@ then @s ## t2 =: sT1@ has type @Stream
    -- T@ and contains structs where the value of @t1@ is that of @sT1@ and the
    -- value of @t2@ is the value that the same field had in @s@, at any point in
    -- time.
    (=:) (ProjectionS s f) v = Op2 (UpdateField typeOf typeOf f) s v
  
    -- | Create a stream where the field of a struct has been updated by applying
    -- a function to it.
    --
    -- For example, if a struct of type @T@ has two fields, @t1@ of type @Int32@
    -- and @t2@ of type @Word8@, and @s@ is a stream of type @Stream T@, and $f$
    -- is a function from @Stream Int32 -> Stream Int32@ then @s ## t2 =$ f@ has
    -- type @Stream T@ and contains structs where the value of @t1@ is that of
    -- @f@ applied to the original value of @t1@ in @s@, and the value of @t2@ is
    -- the value that the same field had in @s@, at any point in time.
    (=$) (ProjectionS s f) op = Op2 (UpdateField typeOf typeOf f) s (op (s # f))
  