2

If I pattern-match against an expression whose type has only one constructor, will that still force the runtime to evaluate the expression to WHNF?


I did an experiment that seems to indicate it doesn't evaluate:

Prelude> data Test = Test Int Int
Prelude> let errorpr () = error "Fail"
Prelude> let makeTest f = let (x,y) = f () in Test x y
Prelude> let x = makeTest errorpr
Prelude> let Test z1 z2 = x
Prelude> :sprint z1
z1 = _
Prelude> :sprint z2
z2 = _
Prelude> :sprint x
x = _

I would have expected to either get an error or :sprint x to yield

x = Test _ _

but it didn't.


Apparently I didn't understand how "let" works. See the answers below

dspyz
  • 5,280
  • 2
  • 25
  • 63

2 Answers2

6
Prelude> let x = makeTest errorpr
Prelude> let Test z1 z2 = x

The last line does not force the evaluation of anything: patterns within let are (implicitly) lazy patterns (aka irrefutable patterns). Try instead

Prelude> let x = makeTest errorpr
Prelude> case x of Test z1 z2 -> "hello!"
Prelude> :sprint x

and you should observe something like Test _ _, since patterns in case are not lazy ones. By comparison,

Prelude> let x = makeTest errorpr
Prelude> case x of ~(Test z1 z2) -> "hello!"   -- lazy pattern!
Prelude> :sprint x

should print just _, like when using let.

The above holds for data types. Instead, newtypes do not lift the internal type, but directly use the same representation. That is, newtype value construction and pattern-matching are no-ops at runtime: they are roughly erased by the compiler after type checking.

chi
  • 111,837
  • 3
  • 133
  • 218
3

(As the possibility of having many constructors was mentioned, I presumed that the question referred to a data type. The answer is different if a newtype is used. See the comments.)

Yes, it will. Try for example:

data T = C Int                                                                  

unT (C n) = 42                                                                  

main = print $ unT undefined

and you will get an undefined exception, instead of 42.

But of course it depends on the pattern. If you replace the definition of unT with:

unT ~(C n) = 42

using a lazy pattern, the result will be 42. Also, note that a constructor pattern used in a let expression (e.g., let (C n) = something is also equivalent to a lazy pattern.


Regarding the rest of your question, makeTest generates a Test value whose parameters are not used. If they had been used, you'd get an error of course (e.g., had you used print instead of :sprint). The reason why x does not give you Test _ _ is because the let expression in line 5 corresponds to a lazy pattern (section 3.12 in the Haskell 98 Report). It's as if you had written let ~(Test z1 z2) = x. Therefore, x is never evaluated.

nickie
  • 5,608
  • 2
  • 23
  • 37
  • 3
    To add to this: if this is not desirable behavior, `newtype` does not behave this way. – Daniel Wagner May 20 '14 at 21:30
  • I did a different experiment and got the opposite result. Can you explain why? See my updated question. – dspyz May 20 '14 at 21:30
  • 2
    Note that this is different for `newtype`. A newtype doesn't really exist at runtime, so wrapping and unwrapping does not force evaluation to WHNF. If you write `newtype T = C Int`, then the program prints 42. – Twan van Laarhoven May 20 '14 at 21:31
  • `case undefined of {A _ -> ()} == ()` if `A` is a `newtype` because pattern matching on a `newtype` does nothing; it is the same as writing `case undefined of {_ -> ()}`. If `A` is a datatype, then `case undefined of {A _ -> ()} == undefined` because the *value* `A x` is distinct from `x`. – user2407038 May 20 '14 at 22:41