Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Haskell : Will GHC optimize this?

Tags:

haskell

ghc

Can GHC simplify id = (\(a, b) -> (a, b)).(\(a, b) -> (a, b)) into id = \(a, b) -> (a, b)?

What about more complicated case :

id (Just x) = Just x
id Nothing = Nothing

map f (Just x) = Just (f x)
map _ Nothing  = Nothing

Will GHC simplify id . map into map?

I tried to use plain beta reduction but it looks like these terms are irreducible because of the nasty pattern matching.

Therefore I am curious how do GHC's optimization techniques deal with that.

like image 827
Ford O. Avatar asked Dec 14 '22 23:12

Ford O.


1 Answers

You can ask these questions of ghc by running it with -ddump-simpl. This will cause ghc to dump the "core" code that it compiles programs into. Core is an intermediate language between the part of the compiler that reasons about Haskell code and the part of the compiler that transforms that code into machine code.

When I compiled the following with -O2 -ddump-simpl the results surprised me.

tupid1 :: (a, b) -> (a, b)
tupid1 = (\(a, b) -> (a, b))

tupid2 :: (a, b) -> (a, b)
tupid2 = (\(a, b) -> (a, b)) . (\(a, b) -> (a, b))

The resulting core for tupid1 makes a new specialized identity function.

-- RHS size: {terms: 4, types: 7, coercions: 0}
tupid1 :: forall a_aqo b_aqp. (a_aqo, b_aqp) -> (a_aqo, b_aqp)
[GblId,
 Arity=1,
 Caf=NoCafRefs,
 Str=DmdType <S,1*U(U,U)>m,
 Unf=Unf{Src=InlineStable, TopLvl=True, Value=True, ConLike=True,
         WorkFree=True, Expandable=True,
         Guidance=ALWAYS_IF(arity=1,unsat_ok=True,boring_ok=True)
         Tmpl= \ (@ a_ayd)
                 (@ b_aye)
                 (ds_dIl [Occ=Once] :: (a_ayd, b_aye)) ->
                 ds_dIl}]
tupid1 = \ (@ a_ayd) (@ b_aye) (ds_dIl :: (a_ayd, b_aye)) -> ds_dIl

In core the polymorphic type arguments to functions are represented as explicit arguments. tupid1 takes two of these type arguments, named a_ayd and b_aye, for the two type variables a and b in its signature. It also takes a term ds_dIl that has the type of a tuple of those two types (ds_dIl :: (a_ayd, b_aye)) and returns it unmodified.

The surprising result is tupid2 ...

-- RHS size: {terms: 1, types: 0, coercions: 0}
tupid2 :: forall a_aqm b_aqn. (a_aqm, b_aqn) -> (a_aqm, b_aqn)
[GblId,
 Arity=1,
 Caf=NoCafRefs,
 Str=DmdType <S,1*U(U,U)>m,
 Unf=Unf{Src=InlineStable, TopLvl=True, Value=True, ConLike=True,
         WorkFree=True, Expandable=True,
         Guidance=ALWAYS_IF(arity=1,unsat_ok=True,boring_ok=True)
         Tmpl= \ (@ a_axZ) (@ b_ay0) (x_aIw [Occ=Once] :: (a_axZ, b_ay0)) ->
                 x_aIw}]
tupid2 = tupid1

... which ghc simplifies to tupid1! How it deduces that is beyond my immediate knowledge or ability to discover.


The identity example for Maybe

maybeid :: Maybe a -> Maybe a
maybeid (Just x) = Just x
maybeid Nothing = Nothing

Is also simplified to an identity function with no pattern matching

-- RHS size: {terms: 3, types: 4, coercions: 0}
maybeid :: forall a_aqn. Maybe a_aqn -> Maybe a_aqn
[GblId,
 Arity=1,
 Caf=NoCafRefs,
 Str=DmdType <S,1*U>,
 Unf=Unf{Src=InlineStable, TopLvl=True, Value=True, ConLike=True,
         WorkFree=True, Expandable=True,
         Guidance=ALWAYS_IF(arity=1,unsat_ok=True,boring_ok=True)
         Tmpl= \ (@ a_aqI) (ds_dIq [Occ=Once] :: Maybe a_aqI) -> ds_dIq}]
maybeid = \ (@ a_aqI) (ds_dIq :: Maybe a_aqI) -> ds_dIq

The core for map for Maybe isn't interesting for this question

maybemap :: (a -> b) -> Maybe a -> Maybe b
maybemap f (Just x) = Just (f x)
maybemap _ Nothing = Nothing

But if it's composed with maybeid

maybeidmap :: (a -> b) -> Maybe a -> Maybe b
maybeidmap f = maybeid . maybemap f

ghc simplifies it to maybemap

-- RHS size: {terms: 1, types: 0, coercions: 0}
maybeidmap
  :: forall a_aqp b_aqq.
     (a_aqp -> b_aqq) -> Maybe a_aqp -> Maybe b_aqq
[GblId,
 Arity=2,
 Caf=NoCafRefs,
 Str=DmdType <L,1*C1(U)><S,1*U>,
 Unf=Unf{Src=InlineStable, TopLvl=True, Value=True, ConLike=True,
         WorkFree=True, Expandable=True,
         Guidance=ALWAYS_IF(arity=0,unsat_ok=True,boring_ok=True)
         Tmpl= maybemap}]
maybeidmap = maybemap

And it does the same thing if id is composed with f.

maybemapid :: (a -> b) -> Maybe a -> Maybe b
maybemapid f = maybemap (id . f)

The composition with the identity function is removed and the whole function simplifies to maybemap

-- RHS size: {terms: 1, types: 0, coercions: 0}
maybemapid
  :: forall a_aqq b_aqr.
     (a_aqq -> b_aqr) -> Maybe a_aqq -> Maybe b_aqr
[GblId,
 Arity=2,
 Caf=NoCafRefs,
 Str=DmdType <L,1*C1(U)><S,1*U>,
 Unf=Unf{Src=InlineStable, TopLvl=True, Value=True, ConLike=True,
         WorkFree=True, Expandable=True,
         Guidance=ALWAYS_IF(arity=2,unsat_ok=True,boring_ok=False)
         Tmpl= \ (@ a_ar2)
                 (@ b_ar3)
                 (f_aqL [Occ=Once!] :: a_ar2 -> b_ar3)
                 (eta_B1 [Occ=Once!] :: Maybe a_ar2) ->
                 case eta_B1 of _ [Occ=Dead] {
                   Nothing -> GHC.Base.Nothing @ b_ar3;
                   Just x_aqJ [Occ=Once] -> GHC.Base.Just @ b_ar3 (f_aqL x_aqJ)
                 }}]
maybemapid = maybemap
like image 165
Cirdec Avatar answered Dec 28 '22 00:12

Cirdec