packages feed

MultiChor-1.0.0.0: src/Choreography/Polymorphism.hs

-- | Types, functions, and structures for writing choreographies with variable numbers of participants.
module Choreography.Polymorphism where

import Choreography.Choreography
import Choreography.Choreography.Batteries ((*~>))
import Choreography.Core
import Choreography.Locations
import Control.Monad (void)
import Data.Foldable (toList)
import Data.Functor.Compose (Compose (Compose, getCompose))
import Data.Functor.Const (Const (Const, getConst))
import GHC.TypeLits

-- * The root abstraction

-- | A mapping, accessed by `Member` terms, from types (`Symbol`s) to values.
--   The types of the values depend on the indexing type; this relation is expressed by the type-level function @f@.
--   If the types of the values /don't/ depend on the index, use `Quire`.
--   If the types vary only in that they are `Located` at the indexing party, use `Faceted`.
--   `PIndexed` generalizes those two types in a way that's not usually necessary when writing choreographies.
newtype PIndexed ls f = PIndexed {pindex :: PIndex ls f}

-- | An impredicative quantified type. Wrapping it up in `PIndexed` wherever possible will avoid a lot of type errors and headache.
type PIndex ls f = forall l. (KnownSymbol l) => Member l ls -> f l

-- | Sequence computations indexed by parties.
--   Converts a `PIndexed` of computations into a computation yielding a `PIndexed`.
--   Strongly analogous to 'Data.Traversable.sequence'.
--   In most cases, the [choreographic functions](#g:choreographicfunctions) below will be easier to use
--   than messing around with `Data.Functor.Compose.Compose`.
sequenceP ::
  forall b (ls :: [LocTy]) m.
  (KnownSymbols ls, Monad m) =>
  PIndexed ls (Compose m b) ->
  m (PIndexed ls b)
sequenceP (PIndexed f) = case tySpine @ls of
  TyCons -> do
    b <- getCompose $ f First
    PIndexed fTail <- sequenceP (PIndexed $ f . Later)
    let retVal :: PIndex ls b
        retVal First = b
        retVal (Later ltr) = fTail ltr
    pure $ PIndexed retVal
  TyNil -> pure $ PIndexed \case {}

-- * A type-indexed vector type

-- | A collection of values, all of the same type, assigned to each element of the type-level list.
newtype Quire parties a = Quire {asPIndexed :: PIndexed parties (Const a)}

-- | Access a value in a `Quire` by its index.
getLeaf :: (KnownSymbol p) => Quire parties a -> Member p parties -> a
getLeaf (Quire (PIndexed q)) p = getConst $ q p

-- | Package a function as a `Quire`.
stackLeaves :: forall ps a. (forall p. (KnownSymbol p) => Member p ps -> a) -> Quire ps a
stackLeaves f = Quire . PIndexed $ Const . f

-- | Get the head item from a `Quire`.
qHead :: (KnownSymbol p) => Quire (p ': ps) a -> a
qHead (Quire (PIndexed f)) = getConst $ f First

-- | Get the tail of a `Quire`.
qTail :: Quire (p ': ps) a -> Quire ps a
qTail (Quire (PIndexed f)) = Quire . PIndexed $ f . Later

-- | Prepend a value to a `Quire`.
--   The corresponding `Symbol` to bind it to must be provided by type-application if it can't be infered.
qCons :: forall p ps a. a -> Quire ps a -> Quire (p ': ps) a
qCons a (Quire (PIndexed f)) = Quire . PIndexed $ \case
  First -> Const a
  Later mps -> f mps

-- | An empty `Quire`.
qNil :: Quire '[] a
qNil = Quire $ PIndexed \case {}

-- | Apply a function to a single item in a `Quire`.
qModify :: forall p ps a. (KnownSymbol p, KnownSymbols ps) => Member p ps -> (a -> a) -> Quire ps a -> Quire ps a
qModify First f q = f (qHead q) `qCons` qTail q
qModify (Later m) f q = case tySpine @ps of TyCons -> qHead q `qCons` qModify m f (qTail q)

instance forall parties. (KnownSymbols parties) => Functor (Quire parties) where
  fmap f q = case tySpine @parties of
    TyCons -> f (qHead q) `qCons` fmap f (qTail q)
    TyNil -> qNil

instance forall parties. (KnownSymbols parties) => Applicative (Quire parties) where
  pure a = Quire . PIndexed $ const (Const a)
  qf <*> qa = case tySpine @parties of
    TyCons -> qHead qf (qHead qa) `qCons` (qTail qf <*> qTail qa)
    TyNil -> qNil

instance forall parties. (KnownSymbols parties) => Foldable (Quire parties) where
  foldMap f q = case tySpine @parties of
    TyCons -> f (qHead q) <> foldMap f (qTail q)
    TyNil -> mempty

instance forall parties. (KnownSymbols parties) => Traversable (Quire parties) where
  sequenceA q = case tySpine @parties of
    TyCons -> qCons <$> qHead q <*> sequenceA (qTail q)
    TyNil -> pure qNil

instance forall parties a. (KnownSymbols parties, Eq a) => Eq (Quire parties a) where
  q1 == q2 = and $ (==) <$> q1 <*> q2

instance forall parties a. (KnownSymbols parties, Show a) => Show (Quire parties a) where
  show q = show $ toLocs (refl @parties) `zip` toList q

-- Many more instances are possible...

-- * Non-congruent parallel located values

-- | A unified representation of possibly-distinct homogeneous values owned by many parties.
type Faceted parties common a = PIndexed parties (Facet a common)

-- | Repackages `Located` with the type arguments correctly arranged for use with `PIndexed`.
newtype Facet a common p = Facet {getFacet :: Located (p ': common) a}

-- | Get a `Located` value of a `Faceted` at a given location.
localize :: (KnownSymbol l) => Member l ls -> Faceted ls common a -> Located (l ': common) a
localize l (PIndexed f) = getFacet $ f l

-- | In a context where unwrapping located values is possible, get the respective value stored in a `Faceted`.
viewFacet :: (KnownSymbol l) => Unwrap l -> Member l ls -> Faceted ls common a -> a
viewFacet un l = un First . localize l

{-
unsafeFacet :: [Maybe a] -> Member l ls -> Facet a common l -- providing this as a helper function is pretty sketchy, if we don't need it delete it.
unsafeFacet (Just a : _) First = Facet $ wrap a
unsafeFacet (Nothing : _) First = Empty
unsafeFacet (_ : as) (Later l) = unsafeFacet as l
unsafeFacet [] _ = error "The provided list isn't long enough to use as a Faceted over the intended parties."
-}

-- * #choreographicfunctions# Choreographic functions

-- | Perform a local computation at all of a list of parties, yielding a `Faceted`.
parallel ::
  forall ls a ps m.
  (KnownSymbols ls) =>
  -- | The parties who will do the computation must be present in the census.
  Subset ls ps ->
  -- | The local computation has access to the identity of the party in question,
  --   in additon to the usual unwrapper function.
  (forall l. (KnownSymbol l) => Member l ls -> Unwrap l -> m a) -> -- Could promote this to PIndexed too, but ergonomics might be worse?
  Choreo ps m (Faceted ls '[] a)
parallel ls m = fanOut \mls -> locally (inSuper ls mls) (m mls)

-- | Perform a local computation at all of a list of parties, yielding nothing.
parallel_ ::
  forall ls ps m.
  (KnownSymbols ls) =>
  Subset ls ps ->
  (forall l. (KnownSymbol l) => Member l ls -> Unwrap l -> m ()) ->
  Choreo ps m ()
parallel_ ls m = void $ parallel ls m

-- | Perform a local computation, that doesn't use any existing `Located` values and doesn't depend on the respective party's identity,
--   at all of a list of parties, yielding a `Faceted`.
_parallel :: forall ls a ps m. (KnownSymbols ls) => Subset ls ps -> m a -> Choreo ps m (Faceted ls '[] a)
_parallel ls m = parallel ls \_ _ -> m

-- | Perform a given choreography for each of several parties, giving each of them a return value that form a new `Faceted`.
fanOut ::
  (KnownSymbols qs) =>
  -- | The body.  -- kinda sketchy that rs might not be a subset of ps...
  (forall q. (KnownSymbol q) => Member q qs -> Choreo ps m (Located (q ': rs) a)) ->
  Choreo ps m (Faceted qs rs a)
fanOut body = sequenceP (PIndexed $ Compose . (Facet <$>) <$> body)

-- | Perform a given choreography for each of several parties; the return values are known to recipients but not necessarily to the loop-parties.
fanIn ::
  (KnownSymbols qs, KnownSymbols rs) =>
  -- | The recipients.
  Subset rs ps ->
  -- | The body.
  (forall q. (KnownSymbol q) => Member q qs -> Choreo ps m (Located rs a)) ->
  Choreo ps m (Located rs (Quire qs a))
fanIn rs body = do
  (PIndexed x) <- sequenceP (PIndexed $ Compose . (Const <$>) <$> body)
  rs `congruently` \un -> stackLeaves $ \q -> un refl (getConst $ x q)

-- | The owner of a `Quire` sends its elements to their respective parties, resulting in a `Faceted`.
--   This represents the "scatter" idea common in parallel computing contexts.
scatter ::
  forall census sender recipients a m.
  (KnownSymbol sender, KnownSymbols recipients, Show a, Read a) =>
  Member sender census ->
  Subset recipients census ->
  Located '[sender] (Quire recipients a) ->
  Choreo census m (Faceted recipients '[sender] a)
scatter sender recipients values = fanOut \r ->
  (sender, \un -> un First values `getLeaf` r) *~> inSuper recipients r @@ sender @@ nobody

-- | The many owners of a `Faceted` each send their respective values to a constant list of recipients, resulting in a `Quire`.
--   This represents the "gather" idea common in parallel computing contexts.
gather ::
  forall census recipients senders a dontcare m.
  (KnownSymbols senders, KnownSymbols recipients, Show a, Read a) =>
  Subset senders census ->
  Subset recipients census ->
  Faceted senders dontcare a ->
  Choreo census m (Located recipients (Quire senders a)) -- could be Faceted senders recipients instead...
gather senders recipients (PIndexed values) = fanIn recipients \s ->
  (inSuper senders s, getFacet $ values s) ~> recipients