[GHC] #9398: Data.List.cycle is not a good producer

GHC ghc-devs at haskell.org
Mon Aug 4 06:45:24 UTC 2014


#9398: Data.List.cycle is not a good producer
-------------------------------------+-------------------------------------
              Reporter:  dfeuer      |            Owner:
                  Type:  bug         |           Status:  new
              Priority:  normal      |        Milestone:  7.8.4
             Component:              |          Version:  7.8.3
  libraries/base                     |         Keywords:
            Resolution:              |     Architecture:  Unknown/Multiple
      Operating System:              |       Difficulty:  Easy (less than 1
  Unknown/Multiple                   |  hour)
       Type of failure:  Runtime     |       Blocked By:
  performance bug                    |  Related Tickets:
             Test Case:              |
              Blocking:              |
Differential Revisions:              |
-------------------------------------+-------------------------------------

Comment (by dfeuer):

 Replying to [comment:12 nomeata]:
 I wrote some test functions, using the type

 {{{#!hs
 type BuildArg a = forall b . (a -> b -> b) -> b -> b
 }}}

 Generally things look pretty good (lots of Core to look at below, along
 with comments you should take with a grain of salt since I'm just a
 newbie). I found one case where your definition works significantly worse
 than the Prelude's. The bad case I've found:

 {{{#!hs
 main = print $ foldr (+) 0 $ take 30000000 $ map (* 13) $ cycle
 [1,8,4,0,(5::Int)]
 }}}

 For some reason, your `cycle` implementation allocates a lot, but the
 Prelude one runs in constant space.

 The basic test translations:

 {{{#!hs
 cycleBuild :: BuildArg a -> [a]
 cycleBuild g = cycle (build g)
 }}}

 produces

 {{{#!hs
 cycleBuild
 cycleBuild =
   \ @ a_a3jG g_a3hO ->
     letrec {
       cyc_a2iD
       cyc_a2iD = g_a3hO (:) cyc_a2iD; } in
     cyc_a2iD
 }}}

 which is obviously good, and obviously better than the result with
 `Prelude.cycle`, which is

 {{{#!hs
 cycleBuild
 cycleBuild =
   \ @ a_a1Xy g_a1zF ->
     case g_a1zF (:) ([]) of wild_a2aX {
       [] -> cycle1;
       : ipv_a2b3 ipv1_a2b4 ->
         letrec {
           xs'_a2b1
           xs'_a2b1 = ++ wild_a2aX xs'_a2b1; } in
         xs'_a2b1
     }
 }}}

 {{{#!hs
 cycleAugment :: BuildArg a -> [a] -> [a]
 cycleAugment g xs = cycle (augment g xs)
 }}}

 produces

 {{{#!hs
 cycleAugment
 cycleAugment =
   \ @ a_a3j1 g_a3hS xs_a3hT ->
     letrec {
       cyc_a2iD
       cyc_a2iD = g_a3hS (:) (++ xs_a3hT cyc_a2iD); } in
     cyc_a2iD
 }}}

 which I think is likely the best we can do, and clearly better than what
 the Prelude gives:

 {{{#!hs
 cycleAugment
 cycleAugment =
   \ @ a_a1WT g_a1zJ xs_a1zK ->
     case g_a1zJ (:) xs_a1zK of wild_a2aX {
       [] -> cycle1;
       : ipv_a2b3 ipv1_a2b4 ->
         letrec {
           xs'_a2b1
           xs'_a2b1 = ++ wild_a2aX xs'_a2b1; } in
         xs'_a2b1
     }
 }}}

 {{{#!hs
 foldCycleBuild :: (a -> b -> b) -> b -> BuildArg a -> b
 foldCycleBuild c n g = foldr c n (cycle (build g))
 }}}

 produces

 {{{#!hs
 foldCycleBuild
 foldCycleBuild =
   \ @ a_a3jl @ b_a3jm c_a3hP _ g_a3hR ->
     letrec {
       cyc_a2iD
       cyc_a2iD = g_a3hR c_a3hP cyc_a2iD; } in
     cyc_a2iD
 }}}

 which is obviously good, and ''incomparably'' better than what the Prelude
 gives, which I will not paste here because it is very obviously very much
 inferior.

 The rest of the tests are a little harder for me to interpret, because the
 nested letrecs confuse me, but I think they're probably good too.

 {{{#!hs
 foldCycle c n xs = foldr c n (cycle xs)
 }}}

 produces

 {{{#!hs
 $wfoldCycle
 $wfoldCycle =
   \ @ a_a3lf @ b_a3lg w_s3mE w1_s3mG ->
     letrec {
       cyc_a2iD
       cyc_a2iD =
         letrec {
           go_a2GG
           go_a2GG =
             \ ds_a2GH ->
               case ds_a2GH of _ {
                 [] -> cyc_a2iD;
                 : y_a2GM ys_a2GN -> w_s3mE y_a2GM (go_a2GG ys_a2GN)
               }; } in
         go_a2GG w1_s3mG; } in
     cyc_a2iD

 foldCycle
 foldCycle =
   \ @ a_a3lf @ b_a3lg w_s3mE _ w2_s3mG -> $wfoldCycle w_s3mE w2_s3mG
 }}}

 {{{#!hs
 foldCycleAugment :: (a -> b -> b) -> b -> BuildArg a -> [a] -> b
 foldCycleAugment c n g xs = foldr c n (cycle (augment g xs))
 }}}

 produces

 {{{#!hs
 $wfoldCycleAugment
 $wfoldCycleAugment =
   \ @ a_a3hx @ b_a3hy w_s3mD w1_s3mF w2_s3mG ->
     letrec {
       cyc_a2iD
       cyc_a2iD =
         w1_s3mF
           w_s3mD
           (letrec {
              go_a2GG
              go_a2GG =
                \ ds_a2GH ->
                  case ds_a2GH of _ {
                    [] -> cyc_a2iD;
                    : y_a2GM ys_a2GN -> w_s3mD y_a2GM (go_a2GG ys_a2GN)
                  }; } in
            go_a2GG w2_s3mG); } in
     cyc_a2iD

 foldCycleAugment
 foldCycleAugment =
   \ @ a_a3hx @ b_a3hy w_s3mD _ w2_s3mF w3_s3mG ->
     $wfoldCycleAugment w_s3mD w2_s3mF w3_s3mG
 }}}

 which I have no idea what to think of, honestly.

 {{{#!hs
 mapCycle f xs = map f (cycle xs)
 }}}

 produces

 {{{#!hs
 mapCycle
 mapCycle =
   \ @ a_a3lA @ b_a3lB f_a3hJ xs_a3hK ->
     letrec {
       cyc_a2iD
       cyc_a2iD =
         letrec {
           go_a2GG
           go_a2GG =
             \ ds_a2GH ->
               case ds_a2GH of _ {
                 [] -> cyc_a2iD;
                 : y_a2GM ys_a2GN -> : (f_a3hJ y_a2GM) (go_a2GG ys_a2GN)
               }; } in
         go_a2GG xs_a3hK; } in
     cyc_a2iD
 }}}

 which looks sane enough—it just maps over `xs` and then cycles the result.
 I also took a look at

 {{{#!hs
 cycleMap f xs = cycle (map f xs)
 }}}

 This produced exactly the same Core, which I think is a positive sign.

 The Prelude implementation, on the other hand, gives a rather terrible
 result:

 {{{#!hs
 mapCycle
 mapCycle =
   \ @ a_a2aw @ b_a2ax f_a1x7 xs_a1x8 ->
     case xs_a1x8 of wild_a2aV {
       [] -> case cycle1 of wild1_00 { };
       : ipv_a2b1 ipv1_a2b2 ->
         letrec {
           xs'_a2aZ
           xs'_a2aZ = ++ wild_a2aV xs'_a2aZ; } in
         map f_a1x7 xs'_a2aZ
     }
 }}}

 Yes, it actually cycles the argument and then maps over the
 result—horrible in every way.

--
Ticket URL: <http://ghc.haskell.org/trac/ghc/ticket/9398#comment:15>
GHC <http://www.haskell.org/ghc/>
The Glasgow Haskell Compiler


More information about the ghc-tickets mailing list