Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

unsafePerformIO can be optimized out #3220

Open
rvs314 opened this issue Mar 1, 2024 · 4 comments
Open

unsafePerformIO can be optimized out #3220

rvs314 opened this issue Mar 1, 2024 · 4 comments

Comments

@rvs314
Copy link
Contributor

rvs314 commented Mar 1, 2024

Currently, an expression which isn't used is discarded as dead code, even though it includes an unsafePerformIO action like Debug.Trace. While this behavior is understandable, this caveat should at least be documented.

Steps to Reproduce

Build and run the following program:

import Debug.Trace

seq : a -> b -> b
seq x y = y

main : IO ()
main = let f = trace "STEP-ONE" () in
       putStrLn $ f `seq` trace "STEP-TWO" "STEP-THREE"

Expected Behavior

As there's no documentation to the contrary, I'd assume all the code would be executed because of Idris' eager evaluation order.

STEP-ONE
STEP-TWO
STEP-THREE

Observed Behavior

I instead get:

STEP-TWO
STEP-THREE

However, if I change the program to the following:

import Debug.Trace

%noinline
seq : a -> b -> b
seq x y = y

main : IO ()
main = let f = trace "STEP-ONE" () in
       putStrLn $ f `seq` trace "STEP-TWO" "STEP-THREE"

I get the answer I would normally expect. I've tried this out on both the Node and Racket backends, and with other unsafe actions like idris_crash.

@buzden
Copy link
Contributor

buzden commented Mar 2, 2024

I think, observed behavior is expected documented behaviour since let x = val in e is documented to be equivalent to (\x => e) val, and on your case this completely eliminates the whole expression of val. It does not matter whether it contains unsafePerformIO or not, the fact that this expression is not computed fully corresponds the language semantics.

@rvs314
Copy link
Contributor Author

rvs314 commented Mar 5, 2024

this completely eliminates the whole expression of val

Why is that the case? I understand the let binding can be transformed to a lambda-expression, but under traditional eager evaluation semantics, a lambda expression should still fully evaluate its arguments before its body can be run, even when the body doesn't use the arguments. Whether it uses a let binding or a lambda expression shouldn't matter.

@buzden
Copy link
Contributor

buzden commented Mar 5, 2024

under traditional eager evaluation semantics, a lambda expression should still fully evaluate its arguments

Well, that's interesting. I always thought that only functions fully evaluate their arguments, not arbitrary lambda expressions. I always thought that the meaning of a lambda expression is its normal form, and thus applying a lambda expression to an unused argument literally means non-presence of that argument. But maybe I'm wrong in my thoughts, despite I still think my concept is sound.

@rvs314
Copy link
Contributor Author

rvs314 commented Mar 5, 2024

only functions fully evaluate their arguments, not arbitrary lambda expressions.

The order of argument evaluation in an application should be (observationally) the same for any (eager) expression in the function position. The underlying implementation may use different strategies for different kinds of function expressions, but they should always look the same to the user (for example, always performs the same side effects).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants