Reputation: 10354
In a Haskell program which I don't understand that well (yet), I would like a function
myInfo :: Int -> Picture
myInfo mode =
...
to take always 2 seconds longer than normal (to slow down the output).
I looked up Control.Concurrent.threadDelay
, but due to its signature threadDelay :: Int -> IO ()
I cannot figure how to put it in the pure part of the program where the function myInfo
is defined.
Is it possible to slow myInfo
down (by say 2 secs) without taking the function into the impure area of the Haskell program?
The solution does not need to be production-performant. (It is only a temporary measure to understand the automatic run of the program better.)
Upvotes: 0
Views: 220
Reputation: 3010
The fundamental characteristic of a Pure Function in Haskell is that it does not have any side-effects. Whenever that function is called with the same inputs, it will always produce the same output.
Any timing parameters - such as how long it will take for a function to run - are side-effects, as they depend on external factors, such as for instance your system load, available memory and the phase of the moon.
None of these are inputs to your function - and hence, it's outputs may not depend on them.
Furthermore, computations in Haskell are lazy by default - that is, your myInfo
won't even get evaluated until something consumes it's output.
That being said - and before diving in some of the ugly hacks - instead of attempting to add any side-effects to a pure function, the primary focus here should be on what exactly are you trying to accomplish and why is that needed.
Most likely, what you want to do can be achieved by putting the "impure" slowdown into whichever code is calling that pure function.
If it's called from other pure code, keep going "up" until you get to the "IO" monad and do it there.
Due to the "lazy" nature of pure functions, the effects that you want to slow down won't really manifest until you try to pass it's output to "IO".
If you absolutely cannot avoid putting the slow-down into that function, then instead of using that function that allegedly kills a Kitten each time it's called - you might want to look into using STM with a "pulse".
The STM library and it's "Batteries included" counterpart provides synchronization primitives that allow your function retain it's "pureness".
You could for instance use TMVar
to use something similar of a POSIX Semaphore - and flag it from a "pulse" thread.
I am still fairly new to Haskell myself, only started to use it about two and a half years ago, but I had quite a few of those "impure code" issues - and when I spent some time thinking about them and trying write down what it was that I was actually trying to accomplish, I realized there was a much better way of doing it.
/Edit: To address the "add super expensive computation" fallacy.
One might think about "just" adding some "expensive computation" to that function.
But that fails to address two fundamental issues:
First of all, why this is even attempted in the first place. So far, the OP has only asked a curious question about a topic, and he certainly deserves to get the best possible answer to that.
But that doesn't automatically imply that there might not be a better solution to his problem than what he initially came up with. Instead of locking ourselves up in this mindset that we absolutely need to make this function impure - we really ought to explore different options.
Instead of going down this rabbit hole of adding hack after hack to work-around things, we need to go deeper and look at what the fundamental issue is.
There is no guarantee that GHC won't progressively get better at detecting and eliminating such seemingly useless fluff.
To help create a great solution, I have marked this as Community wiki, so please feel free to edit and improve this as much as you want.
Upvotes: 0
Reputation: 15673
Sort of – if this is for debugging you can have it as
myInfo mode = unsafePerformIO $ do
threadDelay 2000000
return $ actualImplementationOfMyInfo mode
However, while this may be occasionally useful for debugging / testing behaviour with degraded performance, keep in mind that GHC assumes functions are referentially transparent, so you can not rely on this behaviour.
For instance, if you have something like
mode = SomeMode
...
f (myInfo mode)
...
g (myInfo mode)
GHC may well decide that since the two calls to myInfo
are identical to only evaluate it once and reuse the result later. If you need predictable time based / sequential behaviour you're not going to get around having to get your logic into IO, one way or another.
Upvotes: 6