27

Background

This question arises from a challenge Brent Yorgey posed at OPLSS: write a function f :: (Int -> Int) -> Bool that distinguishes f undefined from f (\x -> undefined). All of our answers either used seq or something like bang patterns that desugar into seq. For example:

f :: (Int -> Int) -> Bool
f g = g `seq` True

*Main> f undefined
*** Exception: Prelude.undefined
*Main> f (\x -> undefined)
True

The GHC commentary on seq says that

e1 `seq` e2 

used to desugar into

case e1 of { _ -> e2 }

so I tried desugaring manually. It didn't work:

f' g = case g of { _ -> True }

*Main> f' undefined
True
*Main> f' (\x -> undefined)
True

Question

Does this behavior depend on the more complex seq described at the end of the commentary, and if so, how does it work? Could such an f be written without these primitives?

x  `seq` e2 ==> case seq# x RW of (# x, _ #) -> e2    -- Note shadowing!
e1 `seq` e2 ==> case seq# x RW of (# _, _ #) -> e2
Don Stewart
  • 137,316
  • 36
  • 365
  • 468
acfoltzer
  • 5,588
  • 31
  • 48
  • 13
    The GHC commentary is talking about desugaring to Core, where `case e of { ... }` always means "evaluate e". – Mikhail Glushenkov Jun 22 '11 at 15:23
  • 7
    `seq` is a language primitive. If you remove it from the language, you lose the ability to distinguish between those two functions. Some people would be happy about this :-) – luqui Jun 22 '11 at 15:56
  • 2
    @Mikhail, @luqui: Shouldn’t those be answers? – Tsuyoshi Ito Jun 22 '11 at 16:17
  • 1
    Nah, Mikhail's comment is properly a comment, as it corrects a detail in the question rather than answering it. Luke's comment does largely answer the question, and would make a fine answer given a bit of elaboration. – C. A. McCann Jun 22 '11 at 16:23
  • @camccann: I thought that Mikhail’s comment directly resolved the asker’s confusion which had led him to ask this question. – Tsuyoshi Ito Jun 22 '11 at 22:56

2 Answers2

25

seq cannot be implemented in Haskell. Instead, it is a primitive "hook" into evaluation to weak-head normal form in whatever runtime your Haskell is running on. E.g. on GHC it is compiled to a case in GHC Core, which triggers evaluation to the outermost constructor.

Since it can't be implemented in pure Haskell, it is defined (in GHC) as a primop:

pseudoop   "seq"
       a -> b -> b
       { Evaluates its first argument to head normal form, and then returns its second
         argument as the result. }

Since functions don't have a normal form, seq halts evaluation once it reaches one.

Magically available to the compiler. The same goes for other primitives like par or unsafeCoerce, the RealWorld token, forkOn and so on. All the useful stuff.

Don Stewart
  • 137,316
  • 36
  • 365
  • 468
  • 1
    Does this mean that functions are wrapped in a data constructor once they reach Core: "E.g. on GHC it is compiled to a `case` in GHC Core, which triggers evaluation to the outermost constructor."? I remember from studying the STG machine that a `case` can scrutinize an algebraic type or a primitive, so I suppose this would make sense. – acfoltzer Jun 22 '11 at 17:29
  • 1
    Sort of. They're represented in the STG machine as closures of a particular type, `FUN` (and friends). http://hackage.haskell.org/trac/ghc/wiki/Commentary/Rts/Storage/HeapObjects#FunctionClosures – Don Stewart Jun 22 '11 at 17:35
  • My confusion is from this document: http://research.microsoft.com/apps/pubs/default.aspx?id=67083, where the operational semantics specify a way to return a saturated constructor or an unboxed primitive from a case expression. There's just no rule for returning functions, so I suppose either a) they're wrapped in a constructor or b) such a rule was added later? – acfoltzer Jun 22 '11 at 17:43
9

There is a more higher-level description of STG-machine in How to make a fast curry: push/enter vs eval/apply

Figure 2 contains rule CASEANY that works for functions. In this paper "is a value" proposition means either:

  • it is a saturated constructor application
  • it is a function
  • it is a partial function application (which is still a function, semantically)

Unboxed values, including literals are treated specially, more information can be found in Unboxed values as first class citizens

All these are implementation details and are hidden inside compiler (GHC). Haskell's case expression doesn't force it's scrutineer, only pattern-matching and seq do.

Victor Nazarov
  • 825
  • 8
  • 11