{-# LANGUAGE ParallelArrays #-}
{-# OPTIONS_GHC -fvectorise #-}

-- | User level interface to vectorised parallel arrays.
--
--  /WARNING:/ In the current implementation, the functionality provided in
--  this module is tied to the vectoriser pass of GHC, invoked by `-fvectorise`.
--  These functions will not work at all in unvectorised code. To operate on
--  parallel arrays in unvectorised code, use the functions in
--  "Data.Array.Parallel.PArray" and convert between array representations by
--  using `fromPArrayP` and `toPArrayP` from /vectorised/ code.
---
--  The semantic difference between standard Haskell arrays (aka "lazy
--  arrays") and parallel arrays (aka "strict arrays") is that the evaluation
--  of two different elements of a lazy array is independent, whereas in a
--  strict array either non or all elements are evaluated.
--  In other words, when a parallel array is evaluated to WHNF, all its elements
--  will be evaluated to WHNF. The name parallel array indicates that all array
--  elements may, in general, be evaluated to WHNF in parallel without any
--  need to resort to speculative evaluation.  This parallel evaluation
--  semantics is also beneficial in the sequential case, as it facilitates
--  loop-based array processing as known from classic array-based languages,
--  such as Fortran.
--
--  The interface of this module is essentially a variant of the list
--  component of the Prelude, but also includes some functions (such as
--  permutations) that are not provided for lists.  The following list of
--  operations are not supported on parallel arrays, as they would require the
--  infinite parallel arrays: `iterate', `repeat', and `cycle'.
--
--  UGLY HACK ALERT: 
--   Same ugly hack as in 'base:GHC.PArr'!  We could do without in this module by
--   using the type synonym 'PArr' instead of '[::]', but that would lead to
--   significantly worse error message for end users.
--
module Data.Array.Parallel 
        ( module Data.Array.Parallel.Prelude

        -- * Conversions
        , PArray
        , fromPArrayP
        , toPArrayP
        , fromNestedPArrayP
        
        -- * Constructors
        , emptyP
        , singletonP
        , replicateP
        , appendP, (+:+)
        , concatP
        
        -- * Projections
        , lengthP
        , indexP,  (!:)
        , sliceP
        
        -- * Traversals
        , mapP
        , zipWithP
        , crossMapP

        -- * Filtering
        , filterP
        
        -- * Ziping and Unzipping
        , zipP
        , unzipP)
where
-- Primitives needed by the vectoriser.
import Data.Array.Parallel.Prim                 ()      

import Data.Array.Parallel.PArr
import Data.Array.Parallel.Prelude
import Data.Array.Parallel.Lifted
import Data.Array.Parallel.PArray.PData.Base    (PArray(..))


-------------------------------------------------------------------------------
-- IMPORTANT:
--  We only define the signatures of operations on parallel arrays, and give
--  and bodies that convince GHC that these functions don't just diverge.
--  The vectoriser rewrites them to entirely the code given in the VECTORISE
--  pragmas.
--
--  The functions must be eta-expanded, so the right of the binding is
--  something of the final return type. The vectoriser takes the type of the
--  body to determine what PA dictionary to pass.
--
--  We also put bangs (!) on the arguments to indicate to the GHC strictness
--  analyser that these paramters will really be used in the vectorised code.
--
--     This won't work:   mapP       = undefined
--     You need this:     mapP !_ !_ = [::]
--
--  The bindings have NOINLINE pragmas because we never want to use the
--  actual body code (because it's fake anyway).
--

-- Conversions ----------------------------------------------------------------
-- | O(1). Convert between `PArray` and [::] array representations.
fromPArrayP :: PArray a -> [:a:]
fromPArrayP !_  = emptyP
{-# NOINLINE  fromPArrayP #-}
{-# VECTORISE fromPArrayP = fromPArrayPP #-}


-- | O(1). Convert between `PArray` and [::] array representations.
toPArrayP :: [:a:] -> PArray a
toPArrayP !_    = PArray 0# (error "toPArrayP: unvectorised")
{-# NOINLINE  toPArrayP #-}
{-# VECTORISE toPArrayP = toPArrayPP #-}


-- | O(1). Convert between `PArray` and [::] array representations.
fromNestedPArrayP :: PArray (PArray a) -> [:[:a:]:]
fromNestedPArrayP !_ = emptyP
{-# NOINLINE  fromNestedPArrayP #-}
{-# VECTORISE fromNestedPArrayP = fromNestedPArrayPP #-}


-- Constructors ---------------------------------------------------------------
-- | Construct an empty array, with no elements.
emptyP :: [:a:]
emptyP          = emptyPArr
{-# NOINLINE  emptyP #-}
{-# VECTORISE emptyP = emptyPP #-}


-- | Construct an array with a single element.
singletonP :: a -> [:a:]
singletonP      = singletonPArr
{-# NOINLINE  singletonP #-}
{-# VECTORISE singletonP = singletonPP #-}


-- | Construct an array by replicating the given element some number of times.
replicateP :: Int -> a -> [:a:]
replicateP      = replicatePArr
{-# NOINLINE  replicateP #-}
{-# VECTORISE replicateP = replicatePP #-}


-- | Append two arrays.
appendP, (+:+) :: [:a:] -> [:a:] -> [:a:]
(+:+) !_ !_     = emptyP
{-# NOINLINE  (+:+) #-}
{-# VECTORISE (+:+)     = appendPP #-}

appendP !_ !_   = emptyP
{-# NOINLINE  appendP #-}
{-# VECTORISE appendP   = appendPP #-}


-- | Concatenate an array of arrays.
concatP :: [:[:a:]:] -> [:a:]
concatP !_      = emptyP
{-# NOINLINE  concatP #-}
{-# VECTORISE concatP = concatPP #-}


-- Projections ----------------------------------------------------------------
-- | Take the length of an array.
lengthP :: [:a:] -> Int
lengthP = lengthPArr
{-# NOINLINE  lengthP #-}
{-# VECTORISE lengthP   = lengthPP #-}

-- | Lookup a single element from the source array.
indexP, (!:) :: [:a:] -> Int -> a
(!:)            = indexPArr
{-# NOINLINE  (!:) #-}
{-# VECTORISE (!:)      = indexPP #-}

indexP            = indexPArr
{-# NOINLINE  indexP #-}
{-# VECTORISE indexP    = indexPP #-}


-- | Extract a slice from an array.
sliceP :: Int -> Int -> [:a:] -> [:a:]
sliceP !_ !_ !_ = emptyP
{-# NOINLINE sliceP #-}
{-# VECTORISE sliceP    = slicePP #-}


-- Traversals -----------------------------------------------------------------
-- | Apply a worker function to every element of an array.
mapP :: (a -> b) -> [:a:] -> [:b:]
mapP !_ !_              = emptyP
{-# NOINLINE  mapP #-}
{-# VECTORISE mapP      = mapPP #-}

-- | Apply a worker function to every pair of two arrays.
zipWithP :: (a -> b -> c) -> [:a:] -> [:b:] -> [:c:]
zipWithP !_ !_ !_       = emptyP
{-# NOINLINE  zipWithP #-}
{-# VECTORISE zipWithP  = zipWithPP #-}

-- | For every element 'a' apply the function to get an array of 'b' then,
--   and return an array of all the 'a's and 'b's.
crossMapP :: [:a:] -> (a -> [:b:]) -> [:(a, b):]
{-# NOINLINE crossMapP #-}
crossMapP !_ !_ = emptyP
{-# VECTORISE crossMapP = crossMapPP #-}


-- Filtering -----------------------------------------------------------------
-- | Filter an array, keeping only those elements that match the given predicate.
filterP :: (a -> Bool) -> [:a:] -> [:a:]
filterP !_ !_   = emptyP
{-# NOINLINE  filterP #-}
{-# VECTORISE filterP = filterPP #-}


-- Zipping and Unzipping ------------------------------------------------------
-- | Zip a pair of arrays into an array of pairs.
zipP :: [:a:] -> [:b:] -> [:(a, b):]
zipP !_ !_      = emptyP
{-# NOINLINE  zipP #-}
{-# VECTORISE zipP      = zipPP #-}


-- | Unzip an array of pairs into a pair of arrays.
unzipP :: [:(a, b):] -> ([:a:], [:b:])
unzipP !_       = (emptyP, emptyP)
{-# NOINLINE  unzipP #-}
{-# VECTORISE unzipP    = unzipPP #-}