packages feed

raw-feldspar-0.1: examples/Tut3_Vectors.hs

{-# LANGUAGE PartialTypeSignatures #-}

{-# OPTIONS_GHC -fno-warn-partial-type-signatures #-}

module Tut3_Vectors where

-- This file demonstrates:
--
-- * Basic usage of the vector library
-- * Safety assertions in the generated code

import Prelude ()

import Feldspar.Run
import Feldspar.Data.Vector



-- The vector library will be used by most Feldspar programs that process data
-- in vector or matrix form. It provides a high-level interface, largely
-- by Haskell's list library.

-- The vector library consists of three different vector types, each one in a
-- 1-dimensional and a 2-dimensional form:
--
-- * `Manifest` / `Manifest2`
-- * `Pull` / `Pull2`
-- * `Push` / `Push2`
--
-- In the common case when the element type is `Data`, we can use the following
-- shorthands:
--
-- * `DManifest` / `DManifest2`
-- * `DPull` / `DPull2`
-- * `DPush` / `DPush2`
--
-- I.e. we can write `DPull Word32` instead of `Pull (Data Word32)`.
--
-- Each vector type has a different set of operations defined, and the
-- operations are chosen so that each type only supports those which can be
-- carried out rather efficiently for that type.
--
-- Conversion from `Manifest` to `Pull` and from `Pull` to `Push` is always
-- cheap. However, conversion from `Pull`/`Push` to `Manifest` involves writing
-- the whole vector to memory.
--
-- In most cases, the types will guide which type to use when, and conversions
-- are done automatically. For example, `++` is only supported by `Push`, but
-- the operator accepts any two vectors that can be converted to `Push` and it
-- will do the conversion automatically.
--
-- The `Pull` and `Push` types enjoy *guaranteed* fusion. That is, all
-- intermediate vectors of type `Pull` or `Push` are guaranteed not to appear in
-- the generated code. Only `Manifest` vectors will. However, there are cases
-- when fusion leads to duplicated computations, which means that the user may
-- sometimes want to convert to `Manifest` even though this is not demanded by
-- the types.

-- This file gives an introduction to vectors and some illustrating examples.
-- For more detailed information see the documentation:
-- <http://hackage.haskell.org/package/raw-feldspar/docs/Feldspar-Data-Vector.html>



--------------------------------------------------------------------------------

-- Pull vectors support many list-like operations. Here is a function that sums
-- the last 5 elements in a vector:

sumLast5 :: (Num a, Syntax a) => Pull a -> a
sumLast5 = sum . take 5 . reverse

comp_sumLast5 = icompile $ connectStdIO $ return . (sumLast5 :: _ -> Data Int32)

-- Note that all intermediate pull vectors in `sumLast5` have been fused away.
-- The only array in the generated code is the one that holds the input.

sumLast5Run = connectStdIO $ return . (sumLast5 :: _ -> Data Int32)

run_sumLast5 = runCompiled sumLast5Run

-- When running the program, the vector is entered by first giving its length
-- and then as many elements as the given length. For example, this is a valid
-- vector: "4 1 2 3 4\n".

-- If we want the function to work also for `Manifest` vectors, we just change
-- the type:

sumLast5' :: (Pully vec a, Num a, Syntax a) => vec -> a
sumLast5' = sum . take 5 . reverse



--------------------------------------------------------------------------------

-- Compute the sum of the square of the numbers from 1 to n:

sumSq :: Data Word32 -> Data Word32
sumSq n = sum $ map (\x -> x*x) (1...n)

sumSqRun = connectStdIO $ return . sumSq

comp_sumSq = icompile sumSqRun

-- Note that there is not a single array declared in the generated code, only
-- scalars.

-- The type `Word32` is used a lot in the vector library. However, since it
-- usually represents either an index or a length, it goes under the aliases:
-- `Index` and `Length`.



--------------------------------------------------------------------------------

-- Dot product of two vectors:

dotProd :: (Num a, Syntax a) => Pull a -> Pull a -> a
dotProd a b = sum $ zipWith (*) a b

dotProdRun = connectStdIO $ return . uncurry (dotProd :: _ -> _ -> Data Int32)

comp_dotProd = icompile dotProdRun

-- Again, if we want the function to work also for `Manifest` vectors, we just
-- change the type:

dotProd' :: (Pully vec1 a, Pully vec2 a, Num a, Syntax a) => vec1 -> vec2 -> a
dotProd' a b = sum $ zipWith (*) a b

-- This function is available under the name `scProd` in the vector library.



--------------------------------------------------------------------------------

-- Pull vectors support arbitrary reading patterns. For example, `sumLast5`
-- demonstrated how to read a part from the end of a vector.

-- Push vectors, on the other hand, support arbitrary write patterns. For
-- example `++` creates a vector that writes its content using two separate
-- loops -- one for the left operand and one for the right operand.

-- The following function appends two modified compies of a vector to each
-- other:

quirk :: (Pully vec a, Num a, MonadComp m) => vec -> Push m a
quirk vec = reverse vec ++ map (*2) vec

-- `MonadComp` is any monad to which the monad `Comp` can be lifted. `Comp` can
-- be seen as a restricted version of `Run`, supporting only mutable data
-- structures (similarly to `ST` in normal Haskell). Both `Comp` and `Run` are
-- instances of `MonadComp`.

-- The reason why `Push` is parameterized on the monad `m` is that it is
-- possible to embed side-effects into push vectors using the function
-- `sequens`. For more information, see the Haddock documentation for `sequens`.

quirkRun = connectStdIO $ return . (quirk :: DPull Int32 -> Push Run _)

comp_quirk = icompile quirkRun



--------------------------------------------------------------------------------

-- Manifest vectors have a direct representation in memory; i.e. they correspond
-- to an array in the generated code. Manifest vectors can be created in
-- different ways; e.g.:
--
-- * By reading from a file using `readStd` or `fread`
-- * By freezing a mutable vector using `unsafeFreezeSlice`
-- * By writing another vector to memory using `manifest` or `manifestFresh`

-- Writing a vector to memory is often forced by the types. For example, if we
-- want to compose `sumLast5` with `quirk` we find that the types don't match
-- (even if `sumLast5` were overloaded using `Pully`, since there is no instance
-- `Pully Push`).

demandedManifest :: (Num a, Syntax a, MonadComp m) => Pull a -> m a
demandedManifest vec = do
    vec2 <- manifestFresh $ quirk vec
    return $ sumLast5' vec2

-- Here, `vec2` is a manifest vector. One problem with this example is the use
-- of `manifestFresh` which silently allocates a fresh array to hold the
-- manifest vector. This array will be allocated on the stack and not freed
-- until the end of the current block in the generated code.

-- An alternative to `manifestFresh` is `manifest` which takes the location to
-- which the vector should be written as an argument. This allows reusing the
-- same array for different manifest vectors. However, such reuse is dangerous
-- since each call to `manifest` with a given location will invalidate earlier
-- manifest vectors using that location.
--
-- So the user has a choice: Either play safe and use `manifestFresh` or have
-- control over memory usage using `manifest`. Future plans involve
-- incorporating static analyses to rule out unsafe uses of `manifest` allowing
-- users to have both safety and control at the same time.

demandedManifestRun = connectStdIO (demandedManifest :: _ -> Run (Data Int32))

comp_demandedManifest = icompile demandedManifestRun



--------------------------------------------------------------------------------

-- The vector library contains many partial functions. In contrast to normal
-- Haskell, partial functions may not necessarily crash but may silently go on
-- producing bogus results.

-- For example, in this program, we take the head of an empty vector and
-- multiply it by 2:

bottom :: Data Word32 -> Data Word32
bottom n = a*2
  where
    a = head $ take 0 (1...n)

bottomRun = connectStdIO $ return . bottom

comp_bottom = icompile bottomRun

-- Looking at the generated code, we see that it always returns the number 2
-- without complaining.

run_bottom = runCompiled bottomRun

-- However, if we run the code, we'll find that it actually does crash, even
-- giving an informative message: "indexing outside of Pull vector".

-- The reason is that `icompile` and `runCompiled` actually pass different
-- options to the Feldspar compiler. In order to see the assertions in the
-- generated code, we can use `icompile'`:

comp_bottom_withAsserts =
    icompile' def {compilerAssertions = allExcept []} bottomRun

-- And in order to run the program without assertions, we can use
-- `runCompiled'`:

run_bottom_withoutAsserts =
    runCompiled' def {compilerAssertions = select []} def bottomRun

-- Unfortunately, compiling with assertions will usually prevent simplification
-- of the generated code. The compiler also does a poor job of sharing
-- assertions which is why the same assertion may appear many times in the
-- generated code.
--
-- For this reason, assertions are mostly intended to be used during testing.
-- When generating code for deployment, assertions will typically be turned off,
-- given that the code has been thoroughly tested.



--------------------------------------------------------------------------------

testAll = do
    comp_sumLast5
    compareCompiled sumLast5Run (putStr "80")       "7 12 13 14 15 16 17 18\n"
    compareCompiled sumLast5Run (runIO sumLast5Run) "7 22 23 24 25 26 27 28\n"
    comp_sumSq
    compareCompiled sumSqRun (putStr "385")   "10\n"
    compareCompiled sumSqRun (runIO sumSqRun) "20\n"
    comp_dotProd
    compareCompiled dotProdRun (putStr "794")     "3 11 12 13 4 21 22 23 24\n"
    compareCompiled dotProdRun (runIO dotProdRun) "3 11 12 13 4 31 32 33 34\n"
    comp_quirk
    compareCompiled quirkRun (putStr "6 4 3 2 4 6 8 ") "3 2 3 4\n"
    compareCompiled quirkRun (runIO quirkRun)          "3 12 13 14\n"
    comp_demandedManifest
    compareCompiled demandedManifestRun (putStr "23")               "3 2 3 4\n"
    compareCompiled demandedManifestRun (runIO demandedManifestRun) "3 12 13 14\n"
    icompile bottomRun
    comp_bottom_withAsserts