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