    {-# 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
    # :: forall (f :: Symbol) t s.
    (KnownSymbol f, Typed t, Typed s, Struct s) =>
    Stream s -> (s -> Field f t) -> Stream t
    (#) Stream s
    s s -> Field f t
    f = Op1 s t -> Stream s -> Stream t
    forall a1 a.
    (Typed a1, Typed a) =>
    Op1 a1 a -> Stream a1 -> Stream a
    Op1 (Type s -> Type t -> (s -> Field f t) -> Op1 s t
    forall (s :: Symbol) a b.
    KnownSymbol s =>
    Type a -> Type b -> (a -> Field s b) -> Op1 a b
    GetField Type s
    forall a. Typed a => Type a
    typeOf Type t
    forall a. Typed a => Type a
    typeOf s -> Field f t
    f) Stream s
    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
    ## :: forall (f :: Symbol) t s.
    (KnownSymbol f, Typed t, Typed s, Struct s) =>
    Stream s -> (s -> Field f t) -> Projection s (s -> Field f t) t
    (##) = Stream s -> (s -> Field f t) -> Projection s (s -> Field f t) t
    forall s (f :: Symbol) t.
    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.
      =: :: Projection s (s -> Field f t) t -> Stream t -> Stream s
    (=:) (ProjectionS Stream s
    s s -> Field f t
    f) Stream t
    v = Op2 s t s -> Stream s -> Stream t -> Stream s
    forall a1 b a.
    (Typed a1, Typed b, Typed a) =>
    Op2 a1 b a -> Stream a1 -> Stream b -> Stream a
    Op2 (Type s -> Type t -> (s -> Field f t) -> Op2 s t s
    forall b (s :: Symbol) a.
    (Typeable b, KnownSymbol s, Show b) =>
    Type a -> Type b -> (a -> Field s b) -> Op2 a b a
    UpdateField Type s
    forall a. Typed a => Type a
    typeOf Type t
    forall a. Typed a => Type a
    typeOf s -> Field f t
    f) Stream s
    s Stream t
    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.
      =$ :: Projection s (s -> Field f t) t
    -> (Stream t -> Stream t) -> Stream s
    (=$) (ProjectionS Stream s
    s s -> Field f t
    f) Stream t -> Stream t
    op = Op2 s t s -> Stream s -> Stream t -> Stream s
    forall a1 b a.
    (Typed a1, Typed b, Typed a) =>
    Op2 a1 b a -> Stream a1 -> Stream b -> Stream a
    Op2 (Type s -> Type t -> (s -> Field f t) -> Op2 s t s
    forall b (s :: Symbol) a.
    (Typeable b, KnownSymbol s, Show b) =>
    Type a -> Type b -> (a -> Field s b) -> Op2 a b a
    UpdateField Type s
    forall a. Typed a => Type a
    typeOf Type t
    forall a. Typed a => Type a
    typeOf s -> Field f t
    f) Stream s
    s (Stream t -> Stream t
    op (Stream s
    s Stream s -> (s -> Field f t) -> Stream t
    forall (f :: Symbol) t s.
    (KnownSymbol f, Typed t, Typed s, Struct s) =>
    Stream s -> (s -> Field f t) -> Stream t
    # s -> Field f t
    f))