
Copying the list, sorry. I have a lot of trouble replying correctly
with gmail's interface for some reason. :)
On Tue, Jan 10, 2012 at 11:14 AM, Dan Doel
On Tue, Jan 10, 2012 at 5:01 AM, Simon Marlow
wrote: On 09/01/2012 04:46, wren ng thornton wrote:
Shouldn't (# T #) be identical to T?
No, because (# T #) is unlifted, whereas T is lifted. In operational terms, a function that returns (# T #) does not evaluate the T before returning it, but a function returning T does. This is used in GHC for example to fetch a value from an array without evaluating it, for example:
indexArray :: Array# e -> Int# -> (# e #)
I don't really understand this explanation. (# T #) being unlifted would mean it's isomorphic to T under the correspondence e <-> (# e #). _|_ = (# _|_ #) : (# T #), so this works. Does the difference have to do with unboxed types? For instance: foo :: () -> Int# foo _ = foo () bar :: () -> (# Int# #) bar _ = (# foo () #) baz = case bar () of _ -> 5 -- 5 quux = case foo () of _ -> 5 -- non-termination Because in that case, either (# Int# #) is lifted, or the Int# is effectively lifted when inside the unboxed tuple. The latter is a bit of an oddity. -- Dan

On 1/10/12 11:18 AM, Dan Doel wrote:
On Tue, Jan 10, 2012 at 11:14 AM, Dan Doel
wrote: On Tue, Jan 10, 2012 at 5:01 AM, Simon Marlow
wrote: On 09/01/2012 04:46, wren ng thornton wrote:
Shouldn't (# T #) be identical to T?
No, because (# T #) is unlifted, whereas T is lifted. In operational terms, a function that returns (# T #) does not evaluate the T before returning it, but a function returning T does. This is used in GHC for example to fetch a value from an array without evaluating it, for example:
indexArray :: Array# e -> Int# -> (# e #)
I don't really understand this explanation. (# T #) being unlifted would mean it's isomorphic to T under the correspondence e<-> (# e #). _|_ = (# _|_ #) : (# T #), so this works.
With my revised understanding we have the following family of types: T pointed, lifted, lazy (# T #) pointed?, unlifted, lazy !T unpointed, lifted, eager {-#UNPACK#-}!T unpointed, unlifted, eager where the two !T types are only specified in ADTs and through strictness analysis. Though this doesn't explain the difference in operational behavior for their use as return types. -- Live well, ~wren

On January 10, 2012 12:18:13 wren ng thornton wrote:
On 1/10/12 11:18 AM, Dan Doel wrote:
On Tue, Jan 10, 2012 at 11:14 AM, Dan Doel
wrote: On Tue, Jan 10, 2012 at 5:01 AM, Simon Marlow
wrote: On 09/01/2012 04:46, wren ng thornton wrote:
Shouldn't (# T #) be identical to T?
No, because (# T #) is unlifted, whereas T is lifted. In operational terms, a function that returns (# T #) does not evaluate the T before returning it, but a function returning T does. This is used in GHC for example to fetch a
value from an array without evaluating it, for example: indexArray :: Array# e -> Int# -> (# e #)
With my revised understanding we have the following family of types:
T pointed, lifted, lazy (# T #) pointed?, unlifted, lazy !T unpointed, lifted, eager {-#UNPACK#-}!T unpointed, unlifted, eager
where the two !T types are only specified in ADTs and through strictness analysis. Though this doesn't explain the difference in operational behavior for their use as return types.
IIRC, don't functions in the STG machine evaluate their returned values to WHNF as the very fact that a (lazy) function has comes under valuation implies that its result is to be deconstructed (in a case statement)? An unboxed tuple would already be in WHNF, so this would not be the case (it would be wrong to evaluate the arguments as the case statement forcing the valuation may not do more than deconstruct the unboxed tupple). Cheers! -Tyson

On 10/01/2012 16:18, Dan Doel wrote:
Copying the list, sorry. I have a lot of trouble replying correctly with gmail's interface for some reason. :)
On Tue, Jan 10, 2012 at 11:14 AM, Dan Doel
wrote: On Tue, Jan 10, 2012 at 5:01 AM, Simon Marlow
wrote: On 09/01/2012 04:46, wren ng thornton wrote:
Shouldn't (# T #) be identical to T?
No, because (# T #) is unlifted, whereas T is lifted. In operational terms, a function that returns (# T #) does not evaluate the T before returning it, but a function returning T does. This is used in GHC for example to fetch a value from an array without evaluating it, for example:
indexArray :: Array# e -> Int# -> (# e #)
I don't really understand this explanation. (# T #) being unlifted would mean it's isomorphic to T under the correspondence e<-> (# e #). _|_ = (# _|_ #) : (# T #), so this works.
Does the difference have to do with unboxed types? For instance:
foo :: () -> Int# foo _ = foo () bar :: () -> (# Int# #) bar _ = (# foo () #)
baz = case bar () of _ -> 5 -- 5 quux = case foo () of _ -> 5 -- non-termination
Because in that case, either (# Int# #) is lifted, or the Int# is effectively lifted when inside the unboxed tuple. The latter is a bit of an oddity.
Unboxed types cannot be lifted, so in fact bar compiles to this: bar = \_ -> case foo () of x -> (# x #) and both baz and quux diverge. It might help to understand (# T #) by translating it to (# T, () #). There's really no difference. Cheers, Simon

On January 11, 2012 08:41:04 Simon Marlow wrote:
On 10/01/2012 16:18, Dan Doel wrote:
Does the difference have to do with unboxed types? For instance: foo :: () -> Int# foo _ = foo () bar :: () -> (# Int# #) bar _ = (# foo () #)
baz = case bar () of _ -> 5 -- 5 quux = case foo () of _ -> 5 -- non-termination
Because in that case, either (# Int# #) is lifted, or the Int# is effectively lifted when inside the unboxed tuple. The latter is a bit of an oddity.
Unboxed types cannot be lifted, so in fact bar compiles to this:
bar = \_ -> case foo () of x -> (# x #)
and both baz and quux diverge.
I tried both of these and it seems the lack of a constructor in the case expression results in the evaluation not being forced, so neither diverged. Using a lifted type and adding a construct to the case did the trick though. {-# LANGUAGE MagicHash, UnboxedTuples #-} module Main where import GHC.Exts g :: () -> Int g _ = g () f :: () -> (# Int #) f _ = (# g () #) main_baz :: IO () main_baz = putStrLn $ case g () of (I# _) -> "this one diverges" main_quux :: IO () main_quux = putStrLn $ case f () of (# _ #) -> "this one doesn't diverge" Cheers! -Tyson

On Wed, Jan 11, 2012 at 8:41 AM, Simon Marlow
On 10/01/2012 16:18, Dan Doel wrote:
Copying the list, sorry. I have a lot of trouble replying correctly with gmail's interface for some reason. :)
On Tue, Jan 10, 2012 at 11:14 AM, Dan Doel
wrote: On Tue, Jan 10, 2012 at 5:01 AM, Simon Marlow
wrote: On 09/01/2012 04:46, wren ng thornton wrote:
Shouldn't (# T #) be identical to T?
No, because (# T #) is unlifted, whereas T is lifted. In operational terms, a function that returns (# T #) does not evaluate the T before returning it, but a function returning T does. This is used in GHC for example to fetch a value from an array without evaluating it, for example:
indexArray :: Array# e -> Int# -> (# e #)
I don't really understand this explanation. (# T #) being unlifted would mean it's isomorphic to T under the correspondence e<-> (# e #). _|_ = (# _|_ #) : (# T #), so this works.
Does the difference have to do with unboxed types? For instance:
foo :: () -> Int# foo _ = foo () bar :: () -> (# Int# #) bar _ = (# foo () #)
baz = case bar () of _ -> 5 -- 5 quux = case foo () of _ -> 5 -- non-termination
Because in that case, either (# Int# #) is lifted, or the Int# is effectively lifted when inside the unboxed tuple. The latter is a bit of an oddity.
Unboxed types cannot be lifted, so in fact bar compiles to this:
bar = \_ -> case foo () of x -> (# x #)
and both baz and quux diverge.
It might help to understand (# T #) by translating it to (# T, () #). There's really no difference.
Then I'm afraid I still don't understand the difference. Is it that case in core always evaluates? So: case undefined of x -> ... blows up, while case (# undefined #) of (# x #) -> ... does not? Also, if so, how is (core-wise): foo :: ... -> (# T #) case foo <v> of (# x #) -> ... different from: foo :: ... -> T let x = foo <v> in ... Stack vs. heap allocation? Sorry for being rather thick. -- Dan

On 11/01/2012, at 19:28, Dan Doel wrote:
Then I'm afraid I still don't understand the difference. Is it that case in core always evaluates? So:
case undefined of x -> ...
blows up, while
case (# undefined #) of (# x #) -> ...
does not?
Yes.
Also, if so, how is (core-wise):
foo :: ... -> (# T #) case foo <v> of (# x #) -> ...
different from:
foo :: ... -> T let x = foo <v> in ...
Stack vs. heap allocation?
The second version binds x to a thunk that, when evaluated, calls foo <v> (which yields an evaluated T). The first one calls foo <v> and then binds x to whatever T (possibly unevaluated) it returns. It really is exactly the same as: data Box a = Box a foo :: ... -> Box T case foo <v> of Box x -> ... vs. foo :: ... -> T let x = foo <v> in ... Except that Box T is lifted and (# T #) isn't. Roman
participants (5)
-
Dan Doel
-
Roman Leshchinskiy
-
Simon Marlow
-
Tyson Whitehead
-
wren ng thornton