\_ -> not equivalent to const $

In attempting to devise a variant of cycle which did not keep its argument alive (for the purpose of cycle [1::Int..]), I came across this peculiar behavior: import Debug.Trace cycle' :: (a -> [b]) -> [b] cycle' xs = xs undefined ++ cycle' xs
take 20 $ cycle' (const $ 1:2:3:4:trace "x" 5:[]) [1,2,3,4,x 5,1,2,3,4,5,1,2,3,4,5,1,2,3,4,5]
Nuts. Oh, but wait:
take 20 $ cycle' (\_ -> 1:2:3:4:trace "x" 5:[]) [1,2,3,4,x 5,1,2,3,4,x 5,1,2,3,4,x 5,1,2,3,4,x 5]
Hey, it worked! Can someone explain what the heck is going on here? Luke

On Jan 10, 2008 8:54 PM, Luke Palmer
Can someone explain what the heck is going on here?
AFAICT, nothing is wrong. You see, both returned the very same values. What you saw was in fact the problem with unsafePerformIO and friends, as they may be executed lots of times *or not*. The compiler is free to behave in those two ways for the code with const or with the lambda. But referential transparency wasn't broken at all =). It seems const retained the value, while the lambda didn't. An optimization might transform one into the other if the compiler sees fit. -- Felipe.

On Jan 10, 2008 11:11 PM, Felipe Lessa
On Jan 10, 2008 8:54 PM, Luke Palmer
wrote: Can someone explain what the heck is going on here?
AFAICT, nothing is wrong. You see, both returned the very same values. What you saw was in fact the problem with unsafePerformIO and friends, as they may be executed lots of times *or not*. The compiler is free to behave in those two ways for the code with const or with the lambda. But referential transparency wasn't broken at all =).
Of course. I'm trying to write a variant of cycle that will be efficient with memory for my purposes, so the Haskell 98 standard, which says nothing about memory usage, doesn't really interest me. I'm more interested in what is going on in ghc (6.8.1) in this case, if it has a simple explanation. And of course am interested if there are any better ways to write such a function. Luke

On Jan 11, 2008 2:11 AM, Felipe Lessa
On Jan 10, 2008 8:54 PM, Luke Palmer
wrote: Can someone explain what the heck is going on here?
AFAICT, nothing is wrong. You see, both returned the very same values. [snip] But referential transparency wasn't broken at all =).
Referential transparency wasn't broken, but I wonder what was the compiler, and what were it's options. -- vir http://vir.comtv.ru/

On Jan 10, 2008 11:15 PM, Victor Nazarov
On Jan 11, 2008 2:11 AM, Felipe Lessa
wrote: On Jan 10, 2008 8:54 PM, Luke Palmer
wrote: Can someone explain what the heck is going on here?
AFAICT, nothing is wrong. You see, both returned the very same values. [snip] But referential transparency wasn't broken at all =).
Referential transparency wasn't broken, but I wonder what was the compiler, and what were it's options.
Ahh, it was ghc 6.8.1, without any optimization. If I turn on optimization, the behavior goes away, and they both behave like the const version. Darn. Luke
-- vir http://vir.comtv.ru/

On Jan 10, 2008 9:20 PM, Luke Palmer
Ahh, it was ghc 6.8.1, without any optimization. If I turn on optimization, the behavior goes away, and they both behave like the const version.
That was what I suggested on the last line of my previous e-mail. As const is in Prelude, which probably was compiled with optimizations, it would explain the difference. -- Felipe.

On Jan 11, 2008 2:28 AM, Felipe Lessa
On Jan 10, 2008 9:20 PM, Luke Palmer
wrote: Ahh, it was ghc 6.8.1, without any optimization. If I turn on optimization, the behavior goes away, and they both behave like the const version.
That was what I suggested on the last line of my previous e-mail. As const is in Prelude, which probably was compiled with optimizations, it would explain the difference.
Optimization of const doesn't affect this program behavior. -- vir http://vir.comtv.ru/

On Jan 10, 2008 11:54 PM, Luke Palmer
Can someone explain what the heck is going on here?
Evaluating (const EXPR) creates a closure object with a pointer to 'const' and a pointer to the EXPR thunk. Call this closure object C. Evaluating (C undefined) calls 'const' with the EXPR and C thunks as its arguments, which returns the EXPR thunk. The EXPR thunk is then forced by your code. Evaluating (C undefined) again calls 'const' with the EXPR and C pointers as its arguments again, which again returns the *now forced* EXPR thunk. I.e., evaluating (const EXPR) creates a closure with a pointer to a single EXPR thunk, and then applying the same closure to some argument multiple times uses that same EXPR thunk every time. An unoptimized (\_ -> EXPR) creates a new thunk each time the function is applied to an argument. Hope that helps with understanding what is going on? - Benja

Luke Palmer wrote:
In attempting to devise a variant of cycle which did not keep its argument alive (for the purpose of cycle [1::Int..]), I came across this peculiar behavior:
import Debug.Trace
cycle' :: (a -> [b]) -> [b] cycle' xs = xs undefined ++ cycle' xs
take 20 $ cycle' (const $ 1:2:3:4:trace "x" 5:[]) [1,2,3,4,x 5,1,2,3,4,5,1,2,3,4,5,1,2,3,4,5]
Nuts. Oh, but wait:
take 20 $ cycle' (\_ -> 1:2:3:4:trace "x" 5:[]) [1,2,3,4,x 5,1,2,3,4,x 5,1,2,3,4,x 5,1,2,3,4,x 5]
Hey, it worked!
Can someone explain what the heck is going on here?
Luke
(\_ -> 1:2:3:4:trace "x" 5:[]) literally could mean your second program, but... the 1:2:3:4:trace "x" 5:[] does not depend on the _ argument, and so it can be lifted outside the (\_ -> ... ) and lazily evaluated once and shared between calls. Optimization in ghc do this for you. The definition "const x = (\_ -> x)" binds 'x' outside of the _ argument, so 'x' is obviously outside (\_ -> ...) and will be lazily evaluated once and shared. I see that making the binding and sharing explicit in
take 20 $ cycle' (let x = 1:2:3:4:trace "x" 5:[] in (\_ -> x)) [1,2,3,4,x 5,1,2,3,4,5,1,2,3,4,5,1,2,3,4,5]
behaves like const. And pushing the binding inside the (\_ -> ...)
take 20 $ cycle' (\_ -> let x = 1:2:3:4:trace "x" 5:[] in x) [1,2,3,4,x 5,1,2,3,4,x 5,1,2,3,4,x 5,1,2,3,4,x 5]
behaves like your second example. -- Chris
participants (5)
-
Benja Fallenstein
-
ChrisK
-
Felipe Lessa
-
Luke Palmer
-
Victor Nazarov