#3076: Make genericLength tail-recursive so it doesn't overflow stack
-----------------------------------------+----------------------------------
    Reporter:  Syzygies                  |        Owner:                  
        Type:  run-time performance bug  |       Status:  reopened        
    Priority:  normal                    |    Milestone:                  
   Component:  Compiler                  |      Version:  6.10.1          
    Severity:  normal                    |   Resolution:                  
    Keywords:  genericLength             |   Difficulty:  Unknown         
    Testcase:                            |           Os:  Unknown/Multiple
Architecture:  Unknown/Multiple          |  
-----------------------------------------+----------------------------------
Changes (by Syzygies):

  * keywords:  => genericLength
  * status:  closed => reopened
  * resolution:  invalid =>

Comment:

 That was enlightening. Thanks.

 The library genericLength blows stack on very short lists. My first
 proposal fails on infinite lists, using Peano arithmetic. So they both
 fail reasonable tests. However, one can make a logarithmic improvement in
 the stack usage of the library function, and pass both tests:

 {{{
   module Main where

   import Data.Int

   data Nat = Zero | Succ Nat
     deriving (Show, Eq)

   instance Num Nat where
     fromInteger 0 = Zero
     fromInteger n = Succ $ fromInteger $ n - 1
     Zero + n = n
     Succ m + n = Succ (m + n)
     Zero * _ = Zero
     Succ m * n = n + m * n
     abs n = n
     signum Zero = Zero
     signum (Succ _) = Succ Zero

   instance Ord Nat where
     compare Zero Zero = EQ
     compare Zero _    = LT
     compare _    Zero = GT
     compare (Succ m) (Succ n) = compare m n

   genericLength1       ∷ (Num i) ⇒ [b] → i
   genericLength1 []    =  0
   genericLength1 (_:l) =  1 + genericLength1 l

   genericLength2 ∷ (Num i) ⇒ [b] → i
   genericLength2 = len 0 where
     len n [] = n
     len n (_:xt) = len (n+1) xt

   genericLength3 ∷ (Num i) ⇒ [b] → i
   genericLength3 = len 1 0 where
     len _ n [] = n
     len m n (_:xt)
       | m == n = n + len (n+n) 1 xt
       | otherwise = len m (n+1) xt

   intLength1, intLength2, intLength3 ∷ [a] → Int64
   intLength1 = genericLength1
   intLength2 = genericLength2
   intLength3 = genericLength3

   natLength1, natLength2, natLength3 ∷ [a] → Nat
   natLength1 = genericLength1
   natLength2 = genericLength2
   natLength3 = genericLength3

   list :: Int64 -> [Bool]
   list 0 = []
   list n = True : list (n-1)

   main ∷ IO ()
   main = do

 --  print $ intLength1 $ list $ 2^19 -- fails w/ 8388608 byte stack
     print $ intLength2 $ list $ 2^32
     print $ intLength3 $ list $ 2^32

     print $ natLength1 (repeat 1) > 5
 --  print $ natLength2 (repeat 1) > 5 -- fails
     print $ natLength3 (repeat 1) > 5
 }}}

 Getting this to work requires specializing genericLength, to avoid class
 overhead. This is nevertheless preferable to writing a length function
 from scratch, which might fall into either of these traps.

 Getting this to work also requires a "good producer". I was surprised to
 discover that in this context, [1..n] isn't a "good producer".

-- 
Ticket URL: <http://hackage.haskell.org/trac/ghc/ticket/3076#comment:2>
GHC <http://www.haskell.org/ghc/>
The Glasgow Haskell Compiler
_______________________________________________
Glasgow-haskell-bugs mailing list
[email protected]
http://www.haskell.org/mailman/listinfo/glasgow-haskell-bugs

Reply via email to