packages feed

raw-feldspar-0.2: examples/Tut2_ExpressionsAndTypes.hs

{-# LANGUAGE PartialTypeSignatures #-}

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

module Tut2_ExpressionsAndTypes where

-- This file demonstrates:
--
-- * How to write pure functions in Feldspar
-- * Basic types and the `Syntax` class
-- * How to tell the compiler to link in necessary C libraries

import Prelude ()

import Feldspar.Run



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

-- A function that doubles its argument:

double :: (PrimType a, Num a) => Data a -> Data a
double a = a*2

-- The type constructor `Data` is for pure Feldspar expressions. `Data a` is an
-- expression that will yield a value of type `a` when evaluated.

-- In order to compile `double`, we first need to monomorphize it. This can be
-- conveniently done using a partial type signature. Then we also need to get
-- the input from somewhere and do something with the output. A convenient way
-- to do this is to use `connectStdIO`, which gets the input from `stdin` and
-- writes the output to `stdout`.

doubleRun :: Run ()
doubleRun = connectStdIO $ return . (double :: _ Int32 -> _)

comp_double = icompile doubleRun

run_double  = runCompiled doubleRun



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

type Point a = (a,a)

-- This function computes the distance between two points in the plane:

dist :: (PrimType a, Floating a) => Point (Data a) -> Point (Data a) -> Data a
dist (x1,y1) (x2,y2) = sqrt (dx**2 + dy**2)
  where
    dx = x1-x2
    dy = y1-y2

-- We need to uncurry `dist` before we can use `connectStdIO`:

distRun = connectStdIO $ return . uncurry (dist :: (_ Double, _) -> _)

comp_dist = icompile distRun

-- Note that there are no tuples in the generated code. The coordinates simply
-- become four separate variables. The variables are assigned by reading four
-- numbers in sequence from `stdin`.

-- In order to run compiled code that uses floating-point operations such as
-- `sqrt`, we must tell the C compiler to link in the C math library. We do that
-- as follows:

mathOpts = def {externalFlagsPost = ["-lm"]}

run_dist = runCompiled' def mathOpts distRun

-- The first argument to `runCompiled'` is the options to the Feldspar compiler
-- and the second argument is options to the external compiler. The overloaded
-- value `def` gives the default options for the given type.



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

-- A function computing the nth Fibonacci number can be written using `forLoop`:

fib :: Data Word32 -> Data Word32
fib n = fst $ forLoop n (0,1) $ \_ (a,b) -> (b,a+b)

-- The arguments to `forLoop` are as follows, in order:
--
-- * The number of iterations
-- * The initial state
-- * A step function computing the next state from the loop index and the
--   current state

-- Note the use of ordinary Haskell tuples for constructing and matching on the
-- state. This is made possible by the `Syntax` class by which `forLoop` (and
-- many other functions) is overloaded.

fibRun = connectStdIO $ return . fib

comp_fib = icompile fibRun

-- Note that the pair in the state doesn't appear in the generated code; the
-- state simply consists of two separate variables.



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

-- You'll see that many polymorphic Feldspar functions are overloaded by the
-- `Syntax` class; e.g.:
--
--     forLoop :: Syntax st => Data Length -> st -> (Data Index -> st -> st) -> st
--
-- Intuitively, `Syntax a` can be understood as any type that can be translated
-- to/from Feldspar expressions. Conversion is done using the following
-- functions:
--
--     desugar :: Syntax a => a -> Data (Internal a)
--     sugar   :: Syntax a => Data (Internal a) -> a
--
-- However, there is rarely a need to use these conversions explicitly since
-- functions such as `forLoop` do them internally.

-- For example, the two types
--
--     Data (Int32, Double)
--     (Data Int32, Data Double)
--
-- are isomorphic, and `desugar`/`sugar` convert between the two. However, the
-- latter type is almost always preferred, since it can be constructed and
-- destructed by the normal Haskell constructor `(,)`, as we saw in the `fib`
-- example.



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

testAll = do
    comp_double
    compareCompiled doubleRun (putStr "12")     "6\n"
    compareCompiled doubleRun (runIO doubleRun) "8\n"
    comp_dist
    compareCompiled' def mathOpts distRun' (runIO distRun') "11 12 13 14\n"
    comp_fib
    compareCompiled fibRun (putStr "8")   "6\n"
    compareCompiled fibRun (runIO fibRun) "7\n"
  where
    -- A version of `runDist` that returns an integer to avoid rounding errors
    -- in `compareCompiled`
    distRun' = connectStdIO $ return . conv . uncurry dist
      where
        conv :: Data Double -> Data Word32
        conv = round . (*100000)