1

As far as I know, seq a b evaluates (forces) a and b before returning b. It does not guarantee that a is evaluated first.

pseq a b evaluates a first, then evaluates/returns b.

Now consider the following:

xseq a b = (seq a id) b

Function application needs to evaluate the left operand first (to get a lambda form), and it can't blindly evaluate the right operand before entering the function because that would violate Haskell's non-strict semantics.

Therefore (seq a id) b must evaluate seq a id first, which forces a and id (in some unspecified order (but evaluating id does nothing)), then returns id b (which is b); therefore xseq a b evaluates a before b.

Is xseq a valid implementation of pseq? If not, what's wrong with the above argument (and is it possible to define pseq in terms of seq at all)?

melpomene
  • 84,125
  • 8
  • 85
  • 148
  • If the compiler decides to inline `id` to form `seq a (\x->x)` then it can also see that `\x -> x` is in WHNF so `seq a (\x->x)` could be 'optimized' to just `a`. [`pseq`](https://hackage.haskell.org/package/base-4.9.1.0/docs/src/GHC.Conc.Sync.html#pseq) is actually implemented in terms of `seq` and [`lazy`](https://www.stackage.org/haddock/lts-7.19/ghc-prim-0.5.0.0/src/GHC-Magic.html#lazy), which is a 'deeply magical' identity function which the compiler knows to never inline during strictness analysis. – user2407038 Feb 11 '17 at 21:35
  • The magic you are looking for is really in [`lazy`](http://hackage.haskell.org/package/base-4.9.1.0/docs/GHC-Exts.html#v:lazy). – Alec Feb 11 '17 at 21:39
  • @user2407038 Turning `seq a (\x->x)` into `a` is a type error. Even if it inlines `id`, it would still have to return `\x->x` (and force `a`). That's exactly what I want, isn't it? – melpomene Feb 11 '17 at 21:46
  • 1
    @melpomene Sorry, I meant that `seq a (\x->x) b` can become `seq a ((\x->x) b)` - in general `seq a y o` could become `seq a (y o)` if `y` is in WHNF. Whether the compiler would ever perform such an optimization, I don't know, but it certainly *could*. I believe that in terms of 'raw operational semantics' (i.e. pretending the compiler never changes your code at all) your function is correct - it just breaks down in the presence of optimizations. (indeed, `lazy` is denotationally equal to `id` - so your `xseq` is denotationally equal to `pseq`, and both to `seq`, just not operationally) – user2407038 Feb 11 '17 at 22:00
  • 3
    You seem to assume that `f x` must evaluate `f` first to WHNF, but this might not be the case. If the strictness analyzer proves `f` strict, I think the runtime could evaluate `x` first to WHNF without changing the semantics. I'm unsure about whether this actually happens in the GHC optimizer. – chi Feb 11 '17 at 22:21
  • 1
    I see that five people have downvoted this question so far. I'd be interested to know why. – melpomene Aug 27 '18 at 09:51

2 Answers2

2

The answer seems to be "no, at least not without additional magic".

The problem with

xseq a b = (seq a id) b

is that the compiler can see that the result of seq a id is id, which is strict everywhere. Function application is allowed to evaluate the argument first if the function is strict, because then doing so does not change the semantics of the expression. Therefore an optimizing compiler could start evaluating b first because it knows it will eventually need it.

melpomene
  • 84,125
  • 8
  • 85
  • 148
0

Can pseq be defined in terms of seq?

In GHC - yes.

As noted by Alec, you'll also need the mirror-smoke lazy:

 -- for GHC 8.6.5
import Prelude(seq)
import GHC.Base(lazy)

infixr 0 `pseq`
pseq :: a -> b -> b
pseq x y = x `seq` lazy y

the definition matching its counterpart in the GHC sources; the imports are very different.

For other Haskell implementations, this may work:

import Prelude(seq)

infixr 0 `pseq`
pseq :: a -> b -> b
pseq x y = x `seq` (case x of _ -> y)

possibly in conjunction with - at the very least - the equivalent of:

 -- for GHC 8.6.5
{-# NOINLINE pseq #-}

I'll let melpomene decide if that also qualifies as mirror-smoke...

atravers
  • 455
  • 4
  • 8
  • Any implementation whose `-O0` does not perform strictness analysis should let you define an inefficient version of `lazy` in its own module. `lazy :: a -> a; lazy x = x {-# NOINLINE lazy #-}`. As long as the module doesn't reveal that `lazy` is actually strict in its argument, and doesn't produce an unfolding for it, you should be good. The GHC special is inlining it in Core Prep, right at the end of the core-to-core compilation pipeline. – dfeuer Aug 27 '20 at 16:57