This is a multi-part message in MIME format.
--------------EF4673DF4E1172F415EEF3AF
Content-Type: text/plain; charset=us-ascii
Content-Transfer-Encoding: 7bit

Attached is my idea for removing intermediate lists using what I call
Generators.  I would be very interested if any one has tried something
like this.  I would also be interested in how it compares with Andrew
John Gill's idea presented in his Cheap Deforestation theses and as
currently implemented with CVS version of GHC.

For numeric instance calculations my idea seems to make a BIG
difference.

Ghc compiler writers: As usual, I am having some trouble getting rules
to behave.  Any help would be appreciated.
-- 
Kevin Atkinson
[EMAIL PROTECTED]
http://metalab.unc.edu/kevina/
--------------EF4673DF4E1172F415EEF3AF
Content-Type: text/plain; charset=us-ascii;
 name="Opt.hs"
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="Opt.hs"


{------ Using so called Generators to remove intermediate lists. ------}
{------ Kevin Atkinson, [EMAIL PROTECTED]                        ------}

module Opt where

import Prelude hiding (foldr, foldl, map, filter, take, drop, iterate)
import Maybe (fromJust)

{- The basic idea behind my idea is the Generator type: -}

type Gen h t = (t -> Maybe (h,t), -- generator function
                t )               -- initial value

{- where the generator function takes in an initial state (say a list) 
   and returns a value (the head of a list for example) plus a new state 
   (the tail of the list) or Nothing if there is nothing to return (an
    empty list).  It is the exact same type on function one
   would feed to the unfoldr (defined in the List module) and is very
   similar to an Ananorphism pair except that the generator and the 
   predicate function is wrapped into one function.  

   Now all functions which normal deal with lists will instead work 
   with nothing but generators. -}
   
foldrG :: (a -> b -> b) -> b -> Gen a t -> b
foldrG f v (g,i) = f' i
    where f' i = case g i of 
                 Just (h,t) -> f h (f' t)
                 Nothing    -> v

foldr1G :: (a -> a -> a) -> Gen a t -> a
foldr1G f (g,i) = f' $ fromJust $ g i
    where f' (h,t) = case g t of 
                     Nothing -> h
                     Just v  -> f h (f' v)

foldlG :: (b -> a -> b) -> b -> Gen a t -> b
foldlG f v (g,i) = f' v i
    where f' v i = case g i of
                   Just (h, t) -> f' (f v h) t
                   Nothing     -> v


foldl1G :: (a -> a -> a) -> Gen a t -> a
foldl1G f (g,i) = foldlG f v (g,i') where (v,i') = fromJust$ g i

mapFst f (a,b) = (f a,b)
mapSnd f (a,b) = (a,f b)

mapG :: (h -> i) -> Gen h t -> Gen i t 
mapG f (g,i) = ((\a->fmap (mapFst f) (g a)),i)
               
data TakeG v = TakeG !Int v

takeG :: Int -> Gen h t -> Gen h (TakeG t)
takeG num (g,i) = (g',TakeG 0 i) 
    where g' (TakeG i v) | i == num  = Nothing
                         | otherwise = fmap (mapSnd $ TakeG $ i+1) (g v)

filterG :: (h -> Bool) -> Gen h t -> Gen h t
filterG f (g,i) = (g',i)
    where g' i = case g i of
                 Just (h,t) | f h       -> Just (h,t)
                            | otherwise -> g' t
                 Nothing                -> Nothing

rangeG (a,z) = numRangeG (a, a+1, z)

numRangeG (a,b,z) = (g,a)
    where g i | i > z     = Nothing
              | otherwise = Just (i,i+(b-a))

iterateG f i = ((\i->Just (i,f i)),i)

headG (g,i) = fst$ fromJust$ g i
tailG (g,i) = snd$ fromJust$ g i

lastG = foldr1G (flip const)

{- And to work with normal lists the normal list functions will simply 
   convert the generator to and from a list as necessary  -}

fromG :: Gen a b -> [a]
fromG = foldrG (:) [] 

toG   :: [a] -> Gen a [a]
toG   l   = (g,l) 
    where g (h:t) = Just (h,t)
          g _     = Nothing

foldr f v = foldrG f v . toG

foldl f v = foldlG f v . toG

map f = fromG . mapG f . toG

take n = fromG . takeG n . toG

filter f = fromG . filterG f . toG

intRange :: (Int, Int) -> [Int]
intRange = fromG . rangeG

numRange :: (Ord a, Num a) => (a,a,a) -> [a]
numRange = fromG . numRangeG

iterate f i = fromG $ iterateG f i

{- and finally use the rule: -}

{-# RULES
"toG/fromG"    forall a.  toG (fromG a) = a
 #-}

{- to remove any intermediate conversion to list -}

{- the following inlines seam necessary to make rule work out -}

{-# INLINE fromG #-}
{-# INLINE toG #-}
{-# INLINE foldr #-}
{-# INLINE foldl #-}
{-# INLINE map #-}
{-# INLINE take #-}
{-# INLINE filter #-}
{-# INLINE intRange #-}
{-# INLINE numRange #-}
{-# INLINE iterate #-}

{- 

   Although I have not had a chance to look into Andrew John Gill's
   _Cheap Deforestation for Non-strict Functional Languages_ (1996) in 
   detail, as I see my approach semas to have two distinct advantages:

   1) Generators work with tail-recursive foldl.

   2) They are easier to generalize to other data structures (simply
      create a functions to convert the data structure to and from 
      a generator.

   For numeric instance calculations such as finding numeric approximations
   for integrals, and solutions to differential equations the tail-recursive
   foldl can make a big difference.

   For a demonstration of the difference it can make so the file Main.cc. 
   The integrate with a foldlG is only 25% slower than a direct c or c++
   approach as demonstrated in ctest.cc.

-}

--------------EF4673DF4E1172F415EEF3AF
Content-Type: text/plain; charset=us-ascii;
 name="Main.hs"
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="Main.hs"


module Main where
    
import Prelude hiding (foldr, foldl, map, filter, take, drop, iterate)
import System
import Opt

-- usage ./main <command> <integral> [<rts options>]

{- Use "+RTS -s -RTS" when running the program  to benchmark it.  
   The stats will be dumped in main.stat -}

main = do [command, integral'] <- getArgs
          let integral = read integral'
          print$ case command of
                 "l"  -> integratel  (**2) (integral,1) integral
                 "r"  -> integrater  (**2) (integral,1) integral
                 "l2" -> integratel2 (**2) (integral,1) integral
                 "r2" -> integrater2 (**2) (integral,1) integral
                 "l3" -> integratel3 (**2) (integral,1) integral
                 "r3" -> integrater3 (**2) (integral,1) integral
                 _    -> error "Invalid Command\n"

infixl 1 $>
($>) = flip ($)

integratel :: (Double -> Double) -> (Double,Double) -> Double -> Double
integratel f (a,z) i = numRangeG (a,a+i,z) $> 
                       mapG f              $>
                       mapG (*i)           $>
                       foldlG (+) 0    

integrater :: (Double -> Double) -> (Double,Double) -> Double -> Double
integrater f (a,z) i = numRangeG (a,a+i,z) $> 
                       mapG f              $>
                       mapG (*i)           $>
                       foldrG (+) 0    

--these two replay on the compiler to eliminate toG/fromGs based on the
--rule that it is an idenity however.  However the compiler is not fully
--doing this job as thee are intergates are several times slower

integratel2 :: (Double -> Double) -> (Double,Double) -> Double -> Double
integratel2 f (a,z) i = numRange (a,a+i,z) $> 
                        map f              $>
                        map (*i)           $>
                        foldl (+) 0    

integrater2 :: (Double -> Double) -> (Double,Double) -> Double -> Double
integrater2 f (a,z) i = numRange (a,a+i,z) $> 
                        map f              $>
                        map (*i)           $>
                        foldr (+) 0    

-- these two use standard prelude functions.  You can very easilly run
-- out of stack/heap space when using these for small values of integral

integratel3 :: (Double -> Double) -> (Double,Double) -> Double -> Double
integratel3 f (a,z) i = [a,a+i .. z]       $>
                        Prelude.map f      $>
                        Prelude.map (*i)   $>
                        Prelude.foldl (+) 0 

integrater3 :: (Double -> Double) -> (Double,Double) -> Double -> Double
integrater3 f (a,z) i = [a,a+i .. z]       $>
                        Prelude.map f      $>
                        Prelude.map (*i)   $>
                        Prelude.foldr (+) 0

--------------EF4673DF4E1172F415EEF3AF
Content-Type: text/plain; charset=us-ascii;
 name="ctest.cc"
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="ctest.cc"


#include <iostream>
#include <math.h>
#include <time.h>

// To use simply run ctest.  It will prompt you for the integral and will
// report on the time it took to estimate the integration of x^2 from 
// 0 to 1

int main(int argc, const char * argv[]) {
  double integral;
  cout << "Integral: ";
  cin >> integral;
  double accum = 0;
  clock_t start = clock();
  for (double i = integral; i < 1; i = i + integral) {
    accum = accum + pow(i,2) * integral;
  }
  cout << accum << endl;
  cout << "Time: " << double(clock() - start)/CLOCKS_PER_SEC << endl;
}

--------------EF4673DF4E1172F415EEF3AF
Content-Type: text/plain; charset=us-ascii;
 name="makefile"
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="makefile"


HC      = ghc
HC_OPTS = -recomp -cpp -fglasgow-exts -O2-for-C\
          -O -funfolding-use-threshold24 -fmax-simplifier-iterations6 \
          $(EXTRA_HC_OPTS)

#note: the unfolding-use-threshold really does need to be this high for
#      maxim performance.  I am not sure if setting 
#      max-simplifier-iterations to 6 does any good but the simplifier 
#      was bailing out when it was set to 4 (the default) or 5.
#      Adding -O2-for-c semas to make performace sligtly worse!

SRCS = $(wildcard *.hs)
OBJ2 = $(SRCS:.hs=.o)
OBJS = $(OBJ2:.lhs=.o)

.SUFFIXES : .o .hi .lhs .hc .s

world : main ctest

main : $(OBJS)
        rm -f $@
        $(HC) -o $@ $(HC_OPTS) $(OBJS)

ctest : ctest.cc
        g++ -O2 ctest.cc -o ctest

clean :
        rm -f *.o *.hi 

realclean: clean
        rm -f main ctest main.* core *~ *.bak

depend :
        mkdependHS -- $(HC_OPTS) -- $(SRCS)

# Standard suffix rules
%.hi : %.o
        @:

%.o : %.hs
        $(HC) -c $< $(HC_OPTS)

%.o : %.lhs
        $(HC) -c $< $(HC_OPTS)



























# DO NOT DELETE: Beginning of Haskell dependencies
Main.o : Main.hs
Main.o : ./Opt.hi
Opt.o : Opt.hs
# DO NOT DELETE: End of Haskell dependencies

--------------EF4673DF4E1172F415EEF3AF--



Reply via email to