Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Do inner bang patterns always force outer constructors in Haskell?

In Haskell, is there ever a situation where for a data type

{-# LANGUAGE BangPatterns #-}
import Control.DeepSeq

data D = D Int

the instance

instance NFData D where
  rnf (D !_) = ()

can have a different effect than the instance with another outer !:

instance NFData D where
  rnf !(D !_) = ()

My research:

  • https://downloads.haskell.org/~ghc/8.6.3/docs/html/users_guide/glasgow_exts.html#bang-patterns-informal only talks about let bindings (like this answer), which I think doesn't apply for function pattern matches like this.
  • https://prime.haskell.org/wiki/BangPatterns#Thebasicidea says

    A bang only really has an effect if it precedes a variable or wild-card pattern

    and

    putting a bang before a pattern that forces evaluation anyway does nothing

    and I think

    • rnf (D _) already forces evaluation anyway
      • because it's like rnf x = case x of D _ -> ...
    • so rnf !(D _) would have the same effect as rnf (D _)
    • and thus by substitution rnf !(D !_) must have the same effect as rnf (D !_)

So I think no, these two are always equivalent, but I'm asking anyway to have one super clear answer to refer people to.

like image 595
nh2 Avatar asked Dec 18 '18 22:12

nh2


1 Answers

Indeed this is correct. We can see what is evaluated using :sprint in GHCi, which shows us what thunks have been evaluated.

With no bang patterns:

λ data D = D Int
λ d1 = D 1
λ :sprint d1
d1 = _
λ f1 (D _) = 0
λ f1 d1
0
λ :sprint d1
d1 = <D> _ -- Only D evaluated

With an inner bang pattern:

λ d2 = D 2
λ :sprint d2
d2 = _
λ f2 (D !_) = 0
λ f2 d2
0
λ :sprint d2
d2 = <D> 2 -- Everything evaluated

With an outer bang pattern:

λ d3 = D 3
λ :sprint d3
d3 = _
λ f3 !(D _) = 0
λ f3 d3
0
λ :sprint d3
d3 = <D> _ -- Only D evaluated

With an inner and outer bang patterns:

λ d4 = D 4
λ :sprint d4
d4 = _
λ f4 !(D !_) = 0
λ f4 d4
0
λ :sprint d4
d4 = <D> 4 -- Everything evaluated

From this we can easily see that the patterns !(D !_) and (D !_) are equivalent, and moreover that patterns of the form !(D ...) are redundant.

like image 169
AJF Avatar answered Sep 25 '22 22:09

AJF