Why can't I force an IO action with seq?
Asked Answered
C

2

11

Given this code snippet:

someFunction x = print x `seq` 1

main = do print (someFunction "test")

why doesn't the print x print test when the code is executed?

$./seq_test 
1

If I replace it with error I can check that the left operand of seq is indeed evaluated.

How could I achieve my expected output:

test
1

modifying only someFunction?

Coelacanth answered 2/12, 2013 at 9:7 Comment(3)
Evaluating an IO action is not the same as executing it. There isn't really any way to make someFunction do what you want, because if you want to use print in it, it has to be in the IO monad and you won't be able to print its result (without modifying main).Misti
I wonder why the downvote? I belive my question is a perfectly valid question.Coelacanth
Related: #24776028 .Radii
T
13

Evaluating an IO action does nothing whatsoever. That's right!

If you like, values of IO type are merely "instruction lists". So all you do with that seq is force the program to be sure1 of what should be done if the action was actually used. And using an action has nothing to do with evaluation, it means monadically binding it to the main call. But since, as you say, someFunction is a function with a non-monadic signature, that can't happen here.

What you can do... but don't, is

import Foreign

someFunction x = unsafePerformIO (print x) `seq` 1

this actually couples evaluation to IO execution. Which normally is a really bad idea in Haskell, since evaluation can happen at completely unforseeable order, possibly a different number of times than you think (because the compiler assumes referential transparency), and other mayhem scenarios.

The correct solution is to change the signature to be monadic:

someFunction :: Int -> IO Int
someFunction x = do
     print x
     return 1

main = do
     y <- someFunction "test"
     print y

1And as it happens, the program is as sure as possible anyway, even without seq. Any more details can only be obtained by executing the action.

Topcoat answered 2/12, 2013 at 9:30 Comment(10)
So you are saying that if a library requires a function with a certain non-monadic signature, there is absolutely no way in Haskell to safely add any output during its execution and continue using the library?Coelacanth
Yup, that's right and intentional. Like it or hate it. (Again, there is Debug.Trace, for debugging – but for anything else but debugging this is indeed just as unsafe as any other way to achieve output inside a pure function.) — Seriously, if you write idiomatic Haskell you'll feel much less need for such ad-hoc logging than in other languages.Topcoat
I'd agree if libraries had a good documentation about their monadic features. E.g. Alex and Happy do not provide a good documentation, and I even heard by some users that some of the documentation is actually wrong (which is worse than no-documentation). If I have a requirement about this kind of output I'll then have to resume the do-random-change-and-guess-the-implementation approach in order to use them. If you happen to know how to use them, please answer my other question.Coelacanth
I don't know much anything about Alex and Happy, but... what you're doing sounds indeed horrible. Why don't you just look up the source if you try to find something out about the implementation?Topcoat
Reading the source code cannot be a replacement of the documentation. I'm not a Haskell expert and I don't have days or weeks to spend trying to understand some thousand of lines of code.Coelacanth
I don't know what kind of work you're doing there. Often in Haskell, it's quite easy do understand what an undocumented function does already from the type signature alone, or from toying around with it in GHCi. Inserting trace statements into a full programm OTOH is certainly not more efficient than reading the source code.Topcoat
If you are adding the print statements to get a better understanding of how some complicated code is executing, then that's like debugging. You can use Debug.Trace to get output at random places, and when you understand the code, you remove those calls again.Tanta
@Tanta You got it the wrong way. The output during the parsing phase is a requirement. I do have tried to add other output to debug a bit and tried playing around with the functions in GHCi etc. However, even after this I cannot understand how to use that interface.Coelacanth
If output during parsing is a requirement (it sounds like a strange requirement; you should be able to interleave parsing and printing) then your function has to have IO type.Tanta
using $> from Data.Functor can make the do block pattern of someFunction more terseGreater
G
1

seq evaluated expressions to weak head normal form, which is simply the outermost constructor (or lambda application). The expression print x is already in WHNF, so seq doesn't do anything.

You can get the result you're looking for with the function Debug.Trace.trace.

Gilbertegilbertian answered 2/12, 2013 at 9:31 Comment(8)
It doesn't matter if it's in WHNF for the IO purpose. — trace is a good suggestion – though, as the module name tells, really just for debugging purposes.Topcoat
@Topcoat I know it doesn't matter for the result. But these are tricky issues, and it's better to be precise.Pelerine
@Pelerine I think an IO action is a function value wrapped in a newtype constructor - i.e., clearly WHNFBotelho
@Botelho print is a function, and print x is a function call. There's no constructor anywhere ;)Pelerine
@Pelerine - Sure, print x has type IO () and this is something like IO (\RealWorld -> ...)Botelho
@Ingo: no, he's right: print x ab initio is a thunk referring to application of the print function to the value x.Topcoat
@Topcoat - Yes, that's true, and seq evaluates that thunk to WHNF, which is whatever the representation of a value of type IO () is. You surely don't tell me that IO () does not have some representation, do you?Botelho
@Botelho My remark was about "The expression print x is already in WHNF, so seq doesn't do anything." Which is wrong, because print x is not in WHNF, even though it can certainly evaluated to WHNF, and will be by seq. That's all. Can we end this discussion now?Pelerine

© 2022 - 2024 — McMap. All rights reserved.