Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Does ghc transform a list only used once into a generator for efficiency reasons?

If so, is this a part of the standard or a ghc specific optimisation we can depend on? Or just an optimisation which we can't necessarily depend on.

P.S.: When I tried a test sample, it seemed to indicate that it was taking place/

Prelude> let isOdd x = x `mod` 2 == 1
Prelude> let isEven x = x `mod` 2 == 0
Prelude> ((filter isOdd).(filter isEven)) [1..]

Chews up CPU but doesn't consume much memory.

like image 586
Roman A. Taycher Avatar asked Nov 26 '11 11:11

Roman A. Taycher


1 Answers

Depends on what you mean by generator. The list is lazily generated, and since nothing else references it, the consumed parts are garbage collected almost immediately. Since the result of the above computation doesn't grow, the entire computation runs in constant space. That is not mandated by the standard, but as it is harder to implement nonstrict semantics with different space behaviour for that example (and lots of vaguely similar), in practice you can rely on it.

But normally, the list is still generated as a list, so there's a lot of garbage produced. Under favourable circumstances, ghc eliminates the list [1 .. ] and produces a non-allocating loop:

result :: [Int]
result = filter odd . filter even $ [1 .. ]

(using the Prelude functions out of laziness), compiled with -O2 generates the core

List.result_go =
  \ (x_ayH :: GHC.Prim.Int#) ->
    case GHC.Prim.remInt# x_ayH 2 of _ {
      __DEFAULT ->
        case x_ayH of wild_Xa {
          __DEFAULT -> List.result_go (GHC.Prim.+# wild_Xa 1);
          9223372036854775807 -> GHC.Types.[] @ GHC.Types.Int
        };
      0 ->
        case x_ayH of wild_Xa {
          __DEFAULT -> List.result_go (GHC.Prim.+# wild_Xa 1);
          9223372036854775807 -> GHC.Types.[] @ GHC.Types.Int
        }
    }

A plain loop, running from 1 to maxBound :: Int, producing nothing on the way and [] at the end. It's almost smart enough to plain return []. Note that there's only one division by 2, GHC knows that if an Int is even, it can't be odd, so that check has been eliminated, and in no branch a non-empty list is created (i.e., the unreachable branches have been eliminated by the compiler).

like image 128
Daniel Fischer Avatar answered Sep 29 '22 06:09

Daniel Fischer