Note [Desugar Strict binds]
See https://gitlab.haskell.org/ghc/ghc/wikis/strict-pragma
Desugaring strict variable bindings looks as follows (core below ==>)
let !x = rhs
in body
==>
let x = rhs
in x `seq` body -- seq the variable
and if it is a pattern binding the desugaring looks like
let !pat = rhs
in body
==>
let x = rhs -- bind the rhs to a new variable
pat = x
in x `seq` body -- seq the new variable
if there is no variable in the pattern desugaring looks like
let False = rhs
in body
==>
let x = case rhs of {False -> (); _ -> error "Match failed"}
in x `seq` body
In order to force the Ids in the binding group they are passed around
in the dsHsBind family of functions, and later seq'ed in GHC.HsToCore.Expr.ds_val_bind.
Consider a recursive group like this
letrec
f : g = rhs[f,g]
in <body>
Without `Strict`, we get a translation like this:
let t = /\a. letrec tm = rhs[fm,gm]
fm = case t of fm:_ -> fm
gm = case t of _:gm -> gm
in
(fm,gm)
in let f = /\a. case t a of (fm,_) -> fm
in let g = /\a. case t a of (_,gm) -> gm
in <body>
Here `tm` is the monomorphic binding for `rhs`.
With `Strict`, we want to force `tm`, but NOT `fm` or `gm`.
Alas, `tm` isn't in scope in the `in <body>` part.
The simplest thing is to return it in the polymorphic
tuple `t`, thus:
let t = /\a. letrec tm = rhs[fm,gm]
fm = case t of fm:_ -> fm
gm = case t of _:gm -> gm
in
(tm, fm, gm)
in let f = /\a. case t a of (_,fm,_) -> fm
in let g = /\a. case t a of (_,_,gm) -> gm
in let tm = /\a. case t a of (tm,_,_) -> tm
in tm `seq` <body>
See https://gitlab.haskell.org/ghc/ghc/wikis/strict-pragma for a more
detailed explanation of the desugaring of strict bindings.
Wrinkle 1: forcing linear variables
Consider
let %1 !x = rhs in <body>
==>
let x = rhs in x `seq` <body>
In the desugared version x is used in both arguments of seq. This isn't
recognised a linear. So we can't strictly speaking use seq. Instead, the code is
really desugared as
let x = rhs in case x of x { _ -> <body> }
The shadowing with the case-binder is crucial. The linear linter (see
Note [Linting linearity] in GHC.Core.Lint) understands this as linear. This is
what the seqVar function does.
To be more precise, suppose x has multiplicity p, the fully annotated seqVar (in
Core, p is really stored inside x) is
case x of %p x { _ -> <body> }
In linear Core, case u of %p y { _ -> v } consumes u with multiplicity p, and
makes y available with multiplicity p in v. Which is exactly what we want.
Wrinkle 2: linear patterns
Consider the following linear binding (linear lets are always non-recursive):
let
%1 f : g = rhs
in <body>
The general case would desugar it to
let t = let tm = rhs
fm = case tm of fm:_ -> fm
gm = case tm of _:gm -> gm
in
(tm, fm, gm)
in let f = case t a of (_,fm,_) -> fm
in let g = case t a of (_,_,gm) -> gm
in let tm = case t a of (tm,_,_) -> tm
in tm `seq` <body>
But all the case expression drop variables, which is prohibited by
linearity. But because this is a non-recursive let (in particular we're
desugaring a single binding), we can (and do) desugar the binding as a simple
case-expression instead:
case rhs of {
(f:g) -> <body>
}
This is handled by the special case: a non-recursive PatBind in
GHC.HsToCore.Expr.ds_val_bind. References 1
- Linting linearity GHC.Core.Lint
Referenced by 7
- GHC.HsToCore.Binds call site ×3
- GHC.HsToCore.Utils call site ×2
- GHC.HsToCore.Expr call site
- Multiplicity annotations Language.Haskell.Syntax.Binds