
I got to looking at the `until` function (in GHC.Base):
-- | @'until' p f@ yields the result of applying @f@ until @p@ holds.
until :: (a -> Bool) -> (a -> a) -> a -> a
until p f = go
where
go x | p x = x
| otherwise = go (f x)
This function doesn't immediately *appear* to be strict in its third
argument (`x`), but it actually is:
1. If `p` is lazy, then either it's `const True`, so `until p f x = x`, or
it's `const False`, so `until p f x = _|_`. In the first case, it's
certainly strict in `x`, and in the second case, it goes into an infinite
loop, so it's trivially strict in `x`.
2. If `p` is not lazy, then `until` is obviously strict in `x`.
I wondered, therefore, whether there might be some benefit, for strictness
analysis, to redefining `until`, either like this:
until p f = \ !y -> go y
where
go x | p x = x
| otherwise = go (f x)
or (probably not) like this:
until p f = go
where
go !x | p x = x
| otherwise = go (f x)
The only *semantic* change here is that it can, potentially, replace an
infinite loop with an exception. GHC definitely analyses the strictness
differently; what I can't tell is whether this will actually help it
produce better code in some cases. Specifically, the current `until`
implementation gets
Str=DmdType ,
I don't actually know how to read these, but I can see they're different in
the third argument. The two alternatives are also analyzed differently,
with the `go` function getting different strictness info. I would
speculate, however, that the one that modifies `go` could lead to
double-forcing in the loop in some cases, which would presumably be bad.
David

It's more accurate to say that "until" is *as lazy as* its predicate
argument.
-- infinite loop, slow space leak
until ((== 10) . (!! 3)) [1, 2, 3, 4] (\xs -> xs ++ xs)
-- first-iteration errors out due to strictness of predicate
until ((== 10) . (!! 3)) [1,2,3,undefined] (\xs -> xs ++ xs)
Your proposed changes introduce arbitrary strictness which I don't see any
particular benefit to. Is there a specific example you had in mind where
the behavior is better?
-- Dan Burton
On Sun, Nov 9, 2014 at 11:14 AM, David Feuer
I got to looking at the `until` function (in GHC.Base):
-- | @'until' p f@ yields the result of applying @f@ until @p@ holds. until :: (a -> Bool) -> (a -> a) -> a -> a until p f = go where go x | p x = x | otherwise = go (f x)
This function doesn't immediately *appear* to be strict in its third argument (`x`), but it actually is:
1. If `p` is lazy, then either it's `const True`, so `until p f x = x`, or it's `const False`, so `until p f x = _|_`. In the first case, it's certainly strict in `x`, and in the second case, it goes into an infinite loop, so it's trivially strict in `x`.
2. If `p` is not lazy, then `until` is obviously strict in `x`.
I wondered, therefore, whether there might be some benefit, for strictness analysis, to redefining `until`, either like this:
until p f = \ !y -> go y where go x | p x = x | otherwise = go (f x)
or (probably not) like this:
until p f = go where go !x | p x = x | otherwise = go (f x)
The only *semantic* change here is that it can, potentially, replace an infinite loop with an exception. GHC definitely analyses the strictness differently; what I can't tell is whether this will actually help it produce better code in some cases. Specifically, the current `until` implementation gets
Str=DmdType
, whereas the modified ones get
Str=DmdType
,I don't actually know how to read these, but I can see they're different in the third argument. The two alternatives are also analyzed differently, with the `go` function getting different strictness info. I would speculate, however, that the one that modifies `go` could lead to double-forcing in the loop in some cases, which would presumably be bad.
David
_______________________________________________ Libraries mailing list Libraries@haskell.org http://www.haskell.org/mailman/listinfo/libraries

This is not more accurate. until forces its third argument iff p is strict,
but it's always strict in its third argument, because
until p f _|_ = _|_
for all p and f.
There are two potential reasons to prefer a version that forces its third
argument immediately:
1. Exceptions are usually better than infinite, unproductive loops.
2. It seems conceivable that having more precise strictess information will
help GHC. I don't know if it ever will in this case or not.
On Sun, Nov 9, 2014 at 2:47 PM, Dan Burton
It's more accurate to say that "until" is *as lazy as* its predicate argument.
-- infinite loop, slow space leak until ((== 10) . (!! 3)) [1, 2, 3, 4] (\xs -> xs ++ xs) -- first-iteration errors out due to strictness of predicate until ((== 10) . (!! 3)) [1,2,3,undefined] (\xs -> xs ++ xs)
Your proposed changes introduce arbitrary strictness which I don't see any particular benefit to. Is there a specific example you had in mind where the behavior is better?
-- Dan Burton
On Sun, Nov 9, 2014 at 11:14 AM, David Feuer
wrote: I got to looking at the `until` function (in GHC.Base):
-- | @'until' p f@ yields the result of applying @f@ until @p@ holds. until :: (a -> Bool) -> (a -> a) -> a -> a until p f = go where go x | p x = x | otherwise = go (f x)
This function doesn't immediately *appear* to be strict in its third argument (`x`), but it actually is:
1. If `p` is lazy, then either it's `const True`, so `until p f x = x`, or it's `const False`, so `until p f x = _|_`. In the first case, it's certainly strict in `x`, and in the second case, it goes into an infinite loop, so it's trivially strict in `x`.
2. If `p` is not lazy, then `until` is obviously strict in `x`.
I wondered, therefore, whether there might be some benefit, for strictness analysis, to redefining `until`, either like this:
until p f = \ !y -> go y where go x | p x = x | otherwise = go (f x)
or (probably not) like this:
until p f = go where go !x | p x = x | otherwise = go (f x)
The only *semantic* change here is that it can, potentially, replace an infinite loop with an exception. GHC definitely analyses the strictness differently; what I can't tell is whether this will actually help it produce better code in some cases. Specifically, the current `until` implementation gets
Str=DmdType
, whereas the modified ones get
Str=DmdType
,I don't actually know how to read these, but I can see they're different in the third argument. The two alternatives are also analyzed differently, with the `go` function getting different strictness info. I would speculate, however, that the one that modifies `go` could lead to double-forcing in the loop in some cases, which would presumably be bad.
David
_______________________________________________ Libraries mailing list Libraries@haskell.org http://www.haskell.org/mailman/listinfo/libraries

Neither of your examples have their strictness changed by David's proposed modification, no?
From what I can see with a cursory glance, he's just proposing acknowledging the strictness that is already there but which is buried behind a conditional, and replacing the 'bad' looping bottom that leaks forever and can't be caught with the argument bottom which can be given right away.
-Edward
On Sun, Nov 9, 2014 at 2:47 PM, Dan Burton
It's more accurate to say that "until" is *as lazy as* its predicate argument.
-- infinite loop, slow space leak until ((== 10) . (!! 3)) [1, 2, 3, 4] (\xs -> xs ++ xs) -- first-iteration errors out due to strictness of predicate until ((== 10) . (!! 3)) [1,2,3,undefined] (\xs -> xs ++ xs)
Your proposed changes introduce arbitrary strictness which I don't see any particular benefit to. Is there a specific example you had in mind where the behavior is better?
-- Dan Burton
On Sun, Nov 9, 2014 at 11:14 AM, David Feuer
wrote: I got to looking at the `until` function (in GHC.Base):
-- | @'until' p f@ yields the result of applying @f@ until @p@ holds. until :: (a -> Bool) -> (a -> a) -> a -> a until p f = go where go x | p x = x | otherwise = go (f x)
This function doesn't immediately *appear* to be strict in its third argument (`x`), but it actually is:
1. If `p` is lazy, then either it's `const True`, so `until p f x = x`, or it's `const False`, so `until p f x = _|_`. In the first case, it's certainly strict in `x`, and in the second case, it goes into an infinite loop, so it's trivially strict in `x`.
2. If `p` is not lazy, then `until` is obviously strict in `x`.
I wondered, therefore, whether there might be some benefit, for strictness analysis, to redefining `until`, either like this:
until p f = \ !y -> go y where go x | p x = x | otherwise = go (f x)
or (probably not) like this:
until p f = go where go !x | p x = x | otherwise = go (f x)
The only *semantic* change here is that it can, potentially, replace an infinite loop with an exception. GHC definitely analyses the strictness differently; what I can't tell is whether this will actually help it produce better code in some cases. Specifically, the current `until` implementation gets
Str=DmdType
, whereas the modified ones get
Str=DmdType
,I don't actually know how to read these, but I can see they're different in the third argument. The two alternatives are also analyzed differently, with the `go` function getting different strictness info. I would speculate, however, that the one that modifies `go` could lead to double-forcing in the loop in some cases, which would presumably be bad.
David
_______________________________________________ Libraries mailing list Libraries@haskell.org http://www.haskell.org/mailman/listinfo/libraries
_______________________________________________ Libraries mailing list Libraries@haskell.org http://www.haskell.org/mailman/listinfo/libraries

Replacing an infinite loop with an exception sounds like a benefit to me. While until is technically as strict as the predicate, if the predicate doesn't evaluate at least to WHNF it's "const True|False", so I think David's analysis is correct. WRT strictness analysis, I think the modified annotation means that the final argument will be evaluated at most once. I know that further analysis can benefit from knowledge of one-shot semantics, but without an actual example it's hard for me to say how important that is in this case. John L.
participants (4)
-
Dan Burton
-
David Feuer
-
Edward Kmett
-
John Lato