seq/parametricity properties/free theorems and a proposal/question

The haskell (2010) report says: "The function seq is defined by the equations: seq _|_ b = _|_ seq a b = b, if a /= _|_ seq is usually introduced to improve performance by avoiding unneeded laziness. Strict datatypes (see Section 4.2.1) are defined in terms of the $! operator. However, the provision of seq has important semantic consequences, because it is available at every type. As a consequence, _|_ is not the same as \x -> _|_ since seq can be used to distinguish them. For the same reason, the existence of seq weakens Haskell’s parametricity properties." As has been noted in the literature (and presumably the mailing lists), this has important consequences for free theorems. For example, the property map f . map g = map (f . g) should be derivable from the type of map "(a -> b) -> [a] -> [b]" (i.e., as map works on all a's and b's, it can't actually do anything other than move the a's around, wrap them with the given function to get the b's, or pass them along to other functions which should be similarly constrained). However, this last bit about passing them to similarly constrained functions is not true thanks seq. There is no guarantee map, or some other polymorphic function it invokes has not used seq to look inside the universally quantified types and potentially expose _|_. For example, consider map' f [] = [] map' f (x:xs) = x `seq` f x : map f xs Now "map' f . map' g = map' (f . g)" is not true for all arguments. map' (const 0) . map' (const undefined :: Int -> Int) $ [1..10] = [undefined] map' (const 0 . undefined :: Int -> Int) $ [1..10] = [0,0,0,0,0,0,0,0,0,0] My proposal (although I'm sure someone must have brought this up before, so it's more of a question) is why not take the magic away from seq by making it a class function like deepSeq class Seq a where seq :: a -> b -> b It is easily implemented in haskell by simply forces the constructor for the underlying data type (something also easily derivable by the compiler) instance Seq Int where seq' 0 y = y seq' _ y = y Now, unless I'm missing something, we have restore the strength of Haskell's parametricity properties map :: (a -> b) -> [a] -> [b] map f [] = [] map f (x:xs) = f x : map f xs map' :: Seq a => (a -> b) -> [a] -> [b] map' f [] = [] map' f (x:xs) = x `seq'` f x : map f xs as the full range of operations that can be performed on universally quantified types (in addition to moving them around) is reflected in any explicitly and implicitly (through the dictionaries) passed functions over those types. Thanks! -Tyson

I'm sure someone else will give you a more detailed answer, but what you propose is not so dissimilar to how Seq worked in Haskell 1.4 and Haskell 1.3 (where the class was called Eval). This class constraint was removed for not very good reasons[1] according to my very limited understanding. I would be happy for it to reappear. [1] A History of Haskell: being lazy with class, Section 10.3 On Fri, 18 Mar 2011, Tyson Whitehead wrote:
The haskell (2010) report says:
"The function seq is defined by the equations:
seq _|_ b = _|_ seq a b = b, if a /= _|_
seq is usually introduced to improve performance by avoiding unneeded laziness. Strict datatypes (see Section 4.2.1) are defined in terms of the $! operator. However, the provision of seq has important semantic consequences, because it is available at every type. As a consequence, _|_ is not the same as \x -> _|_ since seq can be used to distinguish them. For the same reason, the existence of seq weakens Haskell’s parametricity properties."
As has been noted in the literature (and presumably the mailing lists), this has important consequences for free theorems. For example, the property
map f . map g = map (f . g)
should be derivable from the type of map "(a -> b) -> [a] -> [b]" (i.e., as map works on all a's and b's, it can't actually do anything other than move the a's around, wrap them with the given function to get the b's, or pass them along to other functions which should be similarly constrained).
However, this last bit about passing them to similarly constrained functions is not true thanks seq. There is no guarantee map, or some other polymorphic function it invokes has not used seq to look inside the universally quantified types and potentially expose _|_. For example, consider
map' f [] = [] map' f (x:xs) = x `seq` f x : map f xs
Now "map' f . map' g = map' (f . g)" is not true for all arguments.
map' (const 0) . map' (const undefined :: Int -> Int) $ [1..10] = [undefined] map' (const 0 . undefined :: Int -> Int) $ [1..10] = [0,0,0,0,0,0,0,0,0,0]
My proposal (although I'm sure someone must have brought this up before, so it's more of a question) is why not take the magic away from seq by making it a class function like deepSeq
class Seq a where seq :: a -> b -> b
It is easily implemented in haskell by simply forces the constructor for the underlying data type (something also easily derivable by the compiler)
instance Seq Int where seq' 0 y = y seq' _ y = y
Now, unless I'm missing something, we have restore the strength of Haskell's parametricity properties
map :: (a -> b) -> [a] -> [b] map f [] = [] map f (x:xs) = f x : map f xs
map' :: Seq a => (a -> b) -> [a] -> [b] map' f [] = [] map' f (x:xs) = x `seq'` f x : map f xs
as the full range of operations that can be performed on universally quantified types (in addition to moving them around) is reflected in any explicitly and implicitly (through the dictionaries) passed functions over those types.
Thanks! -Tyson
-- Russell O'Connor http://r6.ca/ ``All talk about `theft,''' the general counsel of the American Graphophone Company wrote, ``is the merest claptrap, for there exists no property in ideas musical, literary or artistic, except as defined by statute.''

On 18 March 2011 14:54, Tyson Whitehead
map f . map g = map (f . g)
should be derivable from the type of map "(a -> b) -> [a] -> [b]"
I don't think this is true as stated. Consider: map f [] = [] map f [x] = [f x] map f (x:y:zs) = f x : map zs Your proposed property is violated though we have the appropriate type. IIRC I think what is true is that map f . map' g = map' (f . g) For any map' :: (a -> b) -> [a] -> [b]
class Seq a where seq :: a -> b -> b
AFAIK Haskell used to have this but it was removed because a polymorphic seq is just so damn convenient! Furthermore, this solution would not help unless you did not have a Seq (a -> b) instance, but such an instance could be necessary to avoid some space leaks, since you can write stuff like: case holds_on_to_lots_of_memory of True -> \x -> x-1 False -> \x -> x+1 So there are eminently practical reasons for polymorphic seq. Cheers, Max

On 18/03/2011 15:12, Max Bolingbroke wrote:
On 18 March 2011 14:54, Tyson Whitehead
wrote: map f . map g = map (f . g)
should be derivable from the type of map "(a -> b) -> [a] -> [b]"
I don't think this is true as stated. Consider:
map f [] = [] map f [x] = [f x] map f (x:y:zs) = f x : map zs
Your proposed property is violated though we have the appropriate type. IIRC I think what is true is that
map f . map' g = map' (f . g)
For any map' :: (a -> b) -> [a] -> [b]
class Seq a where seq :: a -> b -> b
AFAIK Haskell used to have this but it was removed because a polymorphic seq is just so damn convenient! Furthermore, this solution would not help unless you did not have a Seq (a -> b) instance, but such an instance could be necessary to avoid some space leaks, since you can write stuff like:
case holds_on_to_lots_of_memory of True -> \x -> x-1 False -> \x -> x+1
So there are eminently practical reasons for polymorphic seq.
I agree with you, but there are also eminently practical reasons to *not* have a polymorphic seq. If _|_ == \x._|_, the compiler is free to eta-expand much more often, and eta-expansion is a key optimisation. (though I think GHC violates the rules and eta-expands anyway, at least sometimes). This is one of those times when there's no good answer, or at least, we haven't found one yet. Cheers, Simon

On March 18, 2011 11:12:44 Max Bolingbroke wrote:
On 18 March 2011 14:54, Tyson Whitehead
wrote: map f . map g = map (f . g)
should be derivable from the type of map "(a -> b) -> [a] -> [b]"
I don't think this is true as stated. Consider:
map f [] = [] map f [x] = [f x] map f (x:y:zs) = f x : map zs
Your proposed property is violated though we have the appropriate type. IIRC I think what is true is that
map f . map' g = map' (f . g)
For any map' :: (a -> b) -> [a] -> [b]
You are correct. You don't actually get the "map f . map g = map (f . g)" relationship without looking at the definition of map (which, as you point out, could be otherwise doing things like dropping elements). Ignoring my bad example though, the key, though still is that without seq, the only operations that polymorphic functions can invoke that peer inside their universally quantified arguments are those passed to them. This guarantee gives compilers more freedom in manipulating expressions (through free theorems and, as Simon pointed out, more eta-expansion). Cheers! -Tyson

On March 18, 2011 11:12:44 Max Bolingbroke wrote:
AFAIK Haskell used to have this but it was removed because a polymorphic seq is just so damn convenient! Furthermore, this solution would not help unless you did not have a Seq (a -> b) instance, but such an instance could be necessary to avoid some space leaks, since you can write stuff like:
case holds_on_to_lots_of_memory of True -> \x -> x-1 False -> \x -> x+1
Am I correct in figuring the only issue with giving a compiler supplied "Seq (a -> b)" instance in order to allow forcing the choice between "\x -> x-1" and "\x -> x+1" (and thus freeing the memory) is it invalidates the eta expansion "f = \x -> f x" because "seq _|_ y = _|_" while "seq (\x -> _|_ x) y = y"? (the f's in this email are limited to those with out free x's) That is, it doesn't affect the parenthetic guarantees (to apply it you have to know the underling constructor is ->)? Would it be possible to resolve this by having a compiler supplied "Seq (a -> b)" that performs eta contraction? That is, recursively recognize and expand thunks of the sort "\x -> f x"? (perhaps it is possible that the compiler can know all places it can generates them and all code that can generate them and arranges for them to be tagged) Cheers! -Tyson

On Friday 18 March 2011 2:19:19 PM Tyson Whitehead wrote:
Am I correct in figuring the only issue with giving a compiler supplied "Seq (a -> b)" instance in order to allow forcing the choice between "\x -> x-1" and "\x -> x+1" (and thus freeing the memory) is it invalidates the eta expansion "f = \x -> f x" because "seq _|_ y = _|_" while "seq (\x -> _|_ x) y = y"?
(the f's in this email are limited to those with out free x's)
The problem with having an (a -> b) instance is that it invalidates some of the goals of having the class in the first place: ensuring free theorems. Consider: f :: (a -> b) -> T for some concrete T. The free theorem is: h1 . g1 = g2 . h2 => f g1 = f g2 We can satisfy the precondition with h1 = g2 = const 5, g1 = _|_, and h2 = whatever. Then we have: f _|_ = f (const 5) But, since we have an instance Seq (a -> b), we can write: f :: (a -> b) -> T f g = g `seq` C and this f fails the above free theorem. It doesn't need a constraint because we know which instance we're using, the one for functions. f, of course, also distinguishes functions that would otherwise be extensionally equal, due to the eta thing. Anyhow, the free theorems you get for functions actually depend on the idea that you can't observe them in the way that seq does. -- Dan

On 3/18/11 11:12 AM, Max Bolingbroke wrote:
Furthermore, this solution would not help unless you did not have a Seq (a -> b) instance,
Exactly put.
but such an instance could be necessary to avoid some space leaks, since you can write stuff like:
case holds_on_to_lots_of_memory of True -> \x -> x-1 False -> \x -> x+1
So there are eminently practical reasons for polymorphic seq.
You can do more than avoid memory leaks with this sort of thing. In my current project I use tricks like this extensively in order to perform partial _evaluation_, thereby lifting shared computations out of loops and improving the computational complexity of the program. (As well as constant factors. The constant factors alone give ~10% speedup.) Of course, I am relying on the fact that I will never pass an undefined value to the resulting function (I hope). Otherwise, as mentioned elsewhere, performing the partial evaluation wouldn't be correct. One way to have this cake and eat it too would be if we explicitly distinguished between pointed and unpointed function spaces. It'd be perfectly fine to have, instance Seq (!a -> b) where seq = compilerMagic Since the types ensure that eta conversion won't affect semantics. -- Live well, ~wren

Others have commented on the whys and the wherefores of the removal of the
Eval, so I won't belabor that here, but the History of Haskell paper goes
into a bit more depth, IIRC, talking about how this happened during the
refactoring of a large Haskell project.
You may want to read Johann and Voightlander's "Free theorems in the
presence of seq" and "The impact of seq on free theorems".
http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.3.4936
http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.71.1777
They have done a lot of work on making more rigorous the strictness side
conditions that exist on many free theorems, and show how with a bit of
unrolling and sideways thinking you can still reason about code equationally
using free theorems, even in a world with fully polymorphic seq.
-Edward
On Fri, Mar 18, 2011 at 10:54 AM, Tyson Whitehead
The haskell (2010) report says:
"The function seq is defined by the equations:
seq _|_ b = _|_ seq a b = b, if a /= _|_
seq is usually introduced to improve performance by avoiding unneeded laziness. Strict datatypes (see Section 4.2.1) are defined in terms of the $! operator. However, the provision of seq has important semantic consequences, because it is available at every type. As a consequence, _|_ is not the same as \x -> _|_ since seq can be used to distinguish them. For the same reason, the existence of seq weakens Haskell’s parametricity properties."
As has been noted in the literature (and presumably the mailing lists), this has important consequences for free theorems. For example, the property
map f . map g = map (f . g)
should be derivable from the type of map "(a -> b) -> [a] -> [b]" (i.e., as map works on all a's and b's, it can't actually do anything other than move the a's around, wrap them with the given function to get the b's, or pass them along to other functions which should be similarly constrained).
However, this last bit about passing them to similarly constrained functions is not true thanks seq. There is no guarantee map, or some other polymorphic function it invokes has not used seq to look inside the universally quantified types and potentially expose _|_. For example, consider
map' f [] = [] map' f (x:xs) = x `seq` f x : map f xs
Now "map' f . map' g = map' (f . g)" is not true for all arguments.
map' (const 0) . map' (const undefined :: Int -> Int) $ [1..10] = [undefined] map' (const 0 . undefined :: Int -> Int) $ [1..10] = [0,0,0,0,0,0,0,0,0,0]
My proposal (although I'm sure someone must have brought this up before, so it's more of a question) is why not take the magic away from seq by making it a class function like deepSeq
class Seq a where seq :: a -> b -> b
It is easily implemented in haskell by simply forces the constructor for the underlying data type (something also easily derivable by the compiler)
instance Seq Int where seq' 0 y = y seq' _ y = y
Now, unless I'm missing something, we have restore the strength of Haskell's parametricity properties
map :: (a -> b) -> [a] -> [b] map f [] = [] map f (x:xs) = f x : map f xs
map' :: Seq a => (a -> b) -> [a] -> [b] map' f [] = [] map' f (x:xs) = x `seq'` f x : map f xs
as the full range of operations that can be performed on universally quantified types (in addition to moving them around) is reflected in any explicitly and implicitly (through the dictionaries) passed functions over those types.
Thanks! -Tyson
_______________________________________________ Libraries mailing list Libraries@haskell.org http://www.haskell.org/mailman/listinfo/libraries

On 3/18/11 10:54 AM, Tyson Whitehead wrote:
However, this last bit about passing them to similarly constrained functions is not true thanks seq. There is no guarantee map, or some other polymorphic function it invokes has not used seq to look inside the universally quantified types and potentially expose _|_. For example, consider
map' f [] = [] map' f (x:xs) = x `seq` f x : map f xs
Now "map' f . map' g = map' (f . g)" is not true for all arguments.
Though I'm not sure we should expect it to be. Category theoretically speaking, in general we should not expect the two composition functions to be the same. And indeed we do still get the following functor law: map' f . map' g == map' (f .! g) where (f .! g) x = f $! g x is the composition of the strict subcategory of Haskell. -- Live well, ~wren

Thank you all for all the feedback on this. I very much enjoyed the insights. I finished up Launchbury and Pateron's "Parametricity and Unboxing with Unpointed Types" yesterday and was quite impressed. They seemed to offer a particular elegant framework for dealing with a lot of these issues. I am now reading my way through Johann and Voigtlander's "The Impact of seq on Free Theorems-Based Program Transformations". Someday I'll have to sit down and read "A History of Haskell:being lazy with class" too. Cheers! -Tyson

Tyson Whitehead schrieb:
My proposal (although I'm sure someone must have brought this up before, so it's more of a question) is why not take the magic away from seq by making it a class function like deepSeq
class Seq a where seq :: a -> b -> b
Last round of this discussion was: http://www.haskell.org/pipermail/haskell-cafe/2010-August/081308.html
participants (8)
-
Dan Doel
-
Edward Kmett
-
Henning Thielemann
-
Max Bolingbroke
-
roconnor@theorem.ca
-
Simon Marlow
-
Tyson Whitehead
-
wren ng thornton