Skip to content

Inlining of constant fails when both cross-module and recursive

When an inline recursive function is applied to a constant, that application may reduce if it is in the same module, but will not reduce when in a different module. (Naturally, this harms the ability to split a program into modules while retaining efficiency.)

For example, consider the following files:

T1.hs:

module T1 where

data IntList = Nil | Cons Int IntList

mapIntList :: (Int -> Int) -> IntList -> IntList
mapIntList f Nil = Nil
mapIntList f (Cons x xs) = Cons (f x) (mapIntList f xs)
{-# INLINE mapIntList #-}

mappedNil :: IntList
mappedNil = mapIntList id Nil

T2.hs:

module T2 where

data IntList = Nil | Cons Int IntList

mapIntList :: (Int -> Int) -> IntList -> IntList
mapIntList f Nil = Nil
mapIntList f (Cons x xs) = Cons (f x) (mapIntList f xs)
{-# INLINE mapIntList #-}

T3.hs:

module T3 where

import T2

mappedNil :: IntList
mappedNil = mapIntList id Nil

The program built from T1.hs should be equivalent to the one built from T2.hs and T3.hs; however, the core output from GHC 8.6.1 with -O2 differs significantly.

In the single-module case we obtain:

mappedNil = T1.Nil

Whereas in the two-module case we see:

mappedNil = mapIntList (id @ Int) T2.Nil

Recursion is relevant; the problem disappears if we make this change:

data IntList = Nil | Cons Int Int

mapIntList :: (Int -> Int) -> IntList -> IntList
mapIntList f Nil = Nil
mapIntList f (Cons x xs) = Cons (f x) (f xs)
{-# INLINE mapIntList #-}
Trac metadata
Trac field Value
Version 8.6.1
Type Bug
TypeOfFailure OtherFailure
Priority normal
Resolution Unresolved
Component Compiler
Test case
Differential revisions
BlockedBy
Related
Blocking
CC
Operating system
Architecture
To upload designs, you'll need to enable LFS and have an admin enable hashed storage. More information