Re: [Haskell-cafe] Haskell and the Software design process

From: Rafael Cunha de Almeida
Ivan Miljenovic
disse: On 3 May 2010 14:17, aditya siram
wrote: I'm a little confused about this too. I've seen many functions defined like: f x = (\s -> ...) which is a partial function because it returns a function and is the same as: f x s = ...
No, that's a partially applied function.
A partial function is one such as:
secondElement (_:x:_) = x
Note that it is only defined for lists with at least two elements, for any other list (i.e. singleton or empty) it will throw an error; -fwarn-incomplete-patterns (which is included in -Wall) tells you about these.
You can also argue that functions such as head are partial, as they explicitly throw an error if the input data isn't correct.
Partial functions are bad because if you accidentally use one the wrong way, your entire program crashes in a flaming wreck. It's much better to do something like this:
safeSecondElement (_:x:_) = Just x safeSecondElement _ = Nothing
This will work with all possible input types.
I don't think that safeSecondElement is worse than secondElement. I think it's better for the program to crash right away when you try to do something that doesn't make sense.
Getting the secondElement of a list with one or less elements doesn't make sense, so you are definetely doing something wrong if you expected the list to have two elements, but it doesn't. It's best that the program crashes there, than propagate the error and crash somewhere else or, worse, not crash at all and give a wrong answer.
Stepping in on the tail end of this discussion, however... The reason people argue for safeSecondElement over secondElement is exactly the reason you argue against it. Calling safeSecondElement on a list with < 2 elements forces the programmer to handle the result immediately by returning a Maybe, which requires the programmer to handle the Nothing case, corresponding to invalid input for this function. This is better than the program crashing because the programmer has to fix it before it's even released. This is exactly how to use the type system to your advantage; the error condition is indicated by the return type of the function. "Crashing at the point of the error" isn't necessarily useful in Haskell due to lazy evaluation. The code will crash when the result of the partial function is evaluated, which may be quite far away (in terms of function calls) from where the programmer would expect. John

2010/5/4 John Lato
"Crashing at the point of the error" isn't necessarily useful in Haskell due to lazy evaluation. The code will crash when the result of the partial function is evaluated, which may be quite far away (in terms of function calls) from where the programmer would expect.
Is that why Haskell can't provide a traceback when exceptions are raised? Are there other methods than Maybe or exceptions to handle the errors in Haskell? Is the monad Error(T) useful?

On Tue, May 4, 2010 at 9:09 AM, Limestraël
Are there other methods than Maybe or exceptions to handle the errors in Haskell? Is the monad Error(T) useful?
I believe the usual Error monad is just (Either e), with Left indicating failure. It's the same idea as Maybe, only returning information about the error instead of simply Nothing. At any rate, earlier you said:
The Maybe method is the simplest but it also comes with a little overhead, since you always have to unpack the Maybe a value return
...which sounds odd to me. If you have a complex computation, in which many subcomputations can fail, it makes perfect sense to lift the whole thing into an error-handling monad. Particularly at points where nothing sensible can be done with a Left or Nothing, unpacking the result is unnecessary; instead, leave it as is and continue the computation inside the monad. Then, unpack the value at whatever point that you actually need the result, or can handle errors gracefully. I'd actually argue that error handling with Maybe/Either is the single best, most persuasive use for monadic structure in code--it's certainly the thing I miss most in other languages. For something so simple (the entire implementation of Maybe with instances is shorter than this message!) it's amazingly useful, letting you simplify code while simultaneously having static assurance that you won't get runtime errors because of not checking for failure somewhere. Using "fromJust" or "maybe (error foo) ..." seems bizarre, as if trying to recreate in Haskell the mind-numbing experience of dealing with unexpectedly null pointers being dereferenced. For that matter, null references tend to be frustrating to debug for exactly the same reason that Haskell errors can be: Crashing only when and if the value is actually needed, not when the null pointer or _|_ is first introduced. - C.

On Tue, May 4, 2010 at 2:09 PM, Limestraël
2010/5/4 John Lato
"Crashing at the point of the error" isn't necessarily useful in Haskell due to lazy evaluation. The code will crash when the result of the partial function is evaluated, which may be quite far away (in terms of function calls) from where the programmer would expect.
Is that why Haskell can't provide a traceback when exceptions are raised?
I'm not really the right person to answer this, but I believe this is roughly true.
Are there other methods than Maybe or exceptions to handle the errors in Haskell? Is the monad Error(T) useful?
There are many other ways of handling errors/exceptional conditions. Maybe, Either, ErrorT, abuse of monad fail, explicit-exception [1] and attempt [2] are some of them; there are others. Which is appropriate depends upon the use case. The difficulty is gluing together libraries that use different mechanisms (attempt addresses this to some extent). And of course exceptions, which are unavoidable but often neglected because they're invisible at the type level. [1] http://hackage.haskell.org/package/explicit-exception [2] http://hackage.haskell.org/package/attempt John

On May 4, 2010, at 5:22 AM, John Lato wrote:
The reason people argue for safeSecondElement over secondElement is exactly the reason you argue against it. Calling safeSecondElement on a list with < 2 elements forces the programmer to handle the result immediately by returning a Maybe, which requires the programmer to handle the Nothing case, corresponding to invalid input for this function. This is better than the program crashing because the programmer has to fix it before it's even released. This is exactly how to use the type system to your advantage; the error condition is indicated by the return type of the function.
"Crashing at the point of the error" isn't necessarily useful in Haskell due to lazy evaluation. The code will crash when the result of the partial function is evaluated, which may be quite far away (in terms of function calls) from where the programmer would expect.
Yes, but I think that it is also important to distinguish between cases where an error is expected to be able to occur at runtime, and cases where an error could only occur at runtime *if the programmer screwed up*. If you structure your code to preserve and rely on the invariant that a given list has at least two elements, then it makes sense to call secondElement because if the list doesn't have two elements then you screwed up. Furthermore, there is no way to recover from such an error because you don't necessarily know where the invariant was broken because if you did you would have expected the possibility and already fixed it. But hypothetically, suppose that you decided to use safeSecondElement anyway; now you have to deal with a Nothing in your code. Since, again, you don't know how to recover from this (as if you did, you wouldn't have gotten a Nothing in the first place), the only thing you can do is propagate it through the calculation, until it reaches someone who can recover from it, which means that now your whole calculation has to be muddled up with Maybe types wrapping every result purely to capture the possibility of a bug (or hardware fault, I suppose). If your program relied on this calculation, then it *still* has no choice but to terminate, and it *still* doesn't know where the error occurred --- although if you use something like ErrorT, you might at least know what the nature of the error was. So basically, you still end up with having to terminate your program and printing out an error message reporting the existence of a bug, but now you had to add error-propagating infrastructure to your entire program to do this that makes every function more complicated, rather than relying on the built-in infrastructure supplied by Haskell in the form of undefined, error, and throwing exceptions from pure code. If you want to make your program fault tolerant against bugs --- which is reasonable in programs such as, say, a text editor, where inability to complete one task doesn't necessarily mean that the whole program has to stop --- then you are probably working in the IO monad and so have access to means of catching exceptions, which means that you might as well use them. Thus, if you are dealing with invariants which only fail if you, the programmer, screwed something up, I don't really see the benefit of using functions like safeSecondElement over secondElement. Of course, situations in which you are *expecting* subcomputations to be able to fail at runtime if, say, the input is ill-formed, are a different matter. Cheers, Greg

On Tue, May 4, 2010 at 5:31 PM, Gregory Crosswhite
Yes, but I think that it is also important to distinguish between cases where an error is expected to be able to occur at runtime, and cases where an error could only occur at runtime *if the programmer screwed up*. If you structure your code to preserve and rely on the invariant that a given list has at least two elements, then it makes sense to call secondElement because if the list doesn't have two elements then you screwed up. Furthermore, there is no way to recover from such an error because you don't necessarily know where the invariant was broken because if you did you would have expected the possibility and already fixed it.
But hypothetically, suppose that you decided to use safeSecondElement anyway; now you have to deal with a Nothing in your code. Since, again, you don't know how to recover from this (as if you did, you wouldn't have gotten a Nothing in the first place), the only thing you can do is propagate it through the calculation, until it reaches someone who can recover from it, which means that now your whole calculation has to be muddled up with Maybe types wrapping every result purely to capture the possibility of a bug (or hardware fault, I suppose). If your program relied on this calculation, then it *still* has no choice but to terminate, and it *still* doesn't know where the error occurred --- although if you use something like ErrorT, you might at least know what the nature of the error was. So basically, you still end up with having to terminate your program and printing out an error message reporting the existence of a bug, but now you had to add error-propagating infrastructure to your entire program to do this that makes every function more complicated, rather than relying on the built-in infrastructure supplied by Haskell in the form of undefined, error, and throwing exceptions from pure code.
If you want to make your program fault tolerant against bugs --- which is reasonable in programs such as, say, a text editor, where inability to complete one task doesn't necessarily mean that the whole program has to stop --- then you are probably working in the IO monad and so have access to means of catching exceptions, which means that you might as well use them.
Thus, if you are dealing with invariants which only fail if you, the programmer, screwed something up, I don't really see the benefit of using functions like safeSecondElement over secondElement. Of course, situations in which you are *expecting* subcomputations to be able to fail at runtime if, say, the input is ill-formed, are a different matter.
I agree completely, although I'm not the first to point out that catching exceptions thrown from pure code can be tricky because they might not appear where you expect them to. Of course this can also indicate your architecture needs help. John

This whole thing seems to be touching on something I saw recently and was
quite interested in. I found a site talking about static contract checking
in Haskell, unfortunately I can't seem to find it now, but this paper (
http://research.microsoft.com/en-us/um/people/simonpj/papers/verify/haskellc...)
by Simon Peyton Jones among others seems to be about the same thing
the
site I found was talking about. The idea is to provide enough extra
information to the type system to actually be able to verify that for
instance, secondElement is always called with at least two items. If there
exists in your code any situation in which the contract of a function could
be violated (and therefore the possibility of blowing up at runtime), it no
longer passes static type checking. The paper doesn't go much into the
impact something like that would have on for instance GHCi, but if it was
smart enough to inherit contracts from functions used and display these
derived contracts this would be a very simple way to find all the edge cases
of your code.
-R. Kyle Murphy
--
Curiosity was framed, Ignorance killed the cat.
On Tue, May 4, 2010 at 12:56, John Lato
On Tue, May 4, 2010 at 5:31 PM, Gregory Crosswhite
wrote: Yes, but I think that it is also important to distinguish between cases
where an error is expected to be able to occur at runtime, and cases where an error could only occur at runtime *if the programmer screwed up*. If you structure your code to preserve and rely on the invariant that a given list has at least two elements, then it makes sense to call secondElement because if the list doesn't have two elements then you screwed up. Furthermore, there is no way to recover from such an error because you don't necessarily know where the invariant was broken because if you did you would have expected the possibility and already fixed it.
But hypothetically, suppose that you decided to use safeSecondElement
anyway; now you have to deal with a Nothing in your code. Since, again, you don't know how to recover from this (as if you did, you wouldn't have gotten a Nothing in the first place), the only thing you can do is propagate it through the calculation, until it reaches someone who can recover from it, which means that now your whole calculation has to be muddled up with Maybe types wrapping every result purely to capture the possibility of a bug (or hardware fault, I suppose). If your program relied on this calculation, then it *still* has no choice but to terminate, and it *still* doesn't know where the error occurred --- although if you use something like ErrorT, you might at least know what the nature of the error was. So basically, you still end up with having to terminate your program and printing out an error message reporting the existence of a bug, but now you had to add error-propagating infrastructure to your entire program to do this that makes every function more complicated, rather than relying on the built-in infrastructure supplied by Haskell in the form of undefined, error, and throwing exceptions from pure code.
If you want to make your program fault tolerant against bugs --- which is
reasonable in programs such as, say, a text editor, where inability to complete one task doesn't necessarily mean that the whole program has to stop --- then you are probably working in the IO monad and so have access to means of catching exceptions, which means that you might as well use them.
Thus, if you are dealing with invariants which only fail if you, the
programmer, screwed something up, I don't really see the benefit of using functions like safeSecondElement over secondElement. Of course, situations in which you are *expecting* subcomputations to be able to fail at runtime if, say, the input is ill-formed, are a different matter.
I agree completely, although I'm not the first to point out that catching exceptions thrown from pure code can be tricky because they might not appear where you expect them to. Of course this can also indicate your architecture needs help.
John _______________________________________________ Haskell-Cafe mailing list Haskell-Cafe@haskell.org http://www.haskell.org/mailman/listinfo/haskell-cafe

I definitely like that idea. :-) Is this similar to the notion of dependent types? Cheers, Greg On May 4, 2010, at 11:21 AM, Kyle Murphy wrote:
This whole thing seems to be touching on something I saw recently and was quite interested in. I found a site talking about static contract checking in Haskell, unfortunately I can't seem to find it now, but this paper ( http://research.microsoft.com/en-us/um/people/simonpj/papers/verify/haskellc... ) by Simon Peyton Jones among others seems to be about the same thing the site I found was talking about. The idea is to provide enough extra information to the type system to actually be able to verify that for instance, secondElement is always called with at least two items. If there exists in your code any situation in which the contract of a function could be violated (and therefore the possibility of blowing up at runtime), it no longer passes static type checking. The paper doesn't go much into the impact something like that would have on for instance GHCi, but if it was smart enough to inherit contracts from functions used and display these derived contracts this would be a very simple way to find all the edge cases of your code.
-R. Kyle Murphy -- Curiosity was framed, Ignorance killed the cat.
On Tue, May 4, 2010 at 12:56, John Lato
wrote: On Tue, May 4, 2010 at 5:31 PM, Gregory Crosswhite wrote: Yes, but I think that it is also important to distinguish between cases where an error is expected to be able to occur at runtime, and cases where an error could only occur at runtime *if the programmer screwed up*. If you structure your code to preserve and rely on the invariant that a given list has at least two elements, then it makes sense to call secondElement because if the list doesn't have two elements then you screwed up. Furthermore, there is no way to recover from such an error because you don't necessarily know where the invariant was broken because if you did you would have expected the possibility and already fixed it.
But hypothetically, suppose that you decided to use safeSecondElement anyway; now you have to deal with a Nothing in your code. Since, again, you don't know how to recover from this (as if you did, you wouldn't have gotten a Nothing in the first place), the only thing you can do is propagate it through the calculation, until it reaches someone who can recover from it, which means that now your whole calculation has to be muddled up with Maybe types wrapping every result purely to capture the possibility of a bug (or hardware fault, I suppose). If your program relied on this calculation, then it *still* has no choice but to terminate, and it *still* doesn't know where the error occurred --- although if you use something like ErrorT, you might at least know what the nature of the error was. So basically, you still end up with having to terminate your program and printing out an error message reporting the existence of a bug, but now you had to add error-propagating infrastructure to your entire program to do this that makes every function more complicated, rather than relying on the built-in infrastructure supplied by Haskell in the form of undefined, error, and throwing exceptions from pure code.
If you want to make your program fault tolerant against bugs --- which is reasonable in programs such as, say, a text editor, where inability to complete one task doesn't necessarily mean that the whole program has to stop --- then you are probably working in the IO monad and so have access to means of catching exceptions, which means that you might as well use them.
Thus, if you are dealing with invariants which only fail if you, the programmer, screwed something up, I don't really see the benefit of using functions like safeSecondElement over secondElement. Of course, situations in which you are *expecting* subcomputations to be able to fail at runtime if, say, the input is ill-formed, are a different matter.
I agree completely, although I'm not the first to point out that catching exceptions thrown from pure code can be tricky because they might not appear where you expect them to. Of course this can also indicate your architecture needs help.
John _______________________________________________ Haskell-Cafe mailing list Haskell-Cafe@haskell.org http://www.haskell.org/mailman/listinfo/haskell-cafe

On Tue, May 4, 2010 at 2:43 PM, Gregory Crosswhite
I definitely like that idea. :-) Is this similar to the notion of dependent types?
That's where things tend to wind up eventually, yes. Although, with Haskell as it stands, a great deal of unused information is already present outside the type system, sufficient to automatically prove a range of "easy" properties of code. For instance, consider Neil Mitchell's Catch tool[0], which seems to handle things like the "secondElement" function discussed. Of course, actually writing such a checker is not so easy, and encoding the results of something like Catch in the type system leads to either convoluted type metaprogramming hacks or to new extensions creeping slowly toward full dependent types. - C. [0] http://community.haskell.org/~ndm/catch/

The papers are available here: http://gallium.inria.fr/~naxu/pub.html But in general you can say things like the following: (Dana Xu uses a slightly different notation that I can never remember). sorted :: Ord a => [a] -> Bool
sorted [] = True sorted [x] = True sorted (x:xs) = x < head xs && sorted xs
sort :: Ord a => [a] -> { xs : [a] | sorted xs } sort [] = [] sort (x:xs) = insert x (sort xs)
insert :: Ord a => a -> { xs : [a] | sorted xs } -> { ys : [a] | sorted xs } -- insert :: Ord a => a -> { xs : [a] } -> { ys : [a] | sorted xs ==> sorted ys } insert x [] = [x] insert x yys@(y:ys) | x < y = x:yys | otherwise = y:insert x ys
And with that an abstract interpreter runs checking the partial correctness of the code. The predicates can be specified in Haskell itself, non-termination (even in the predicate) is covered by the fact that you only check partial correctness. In the above, the abstract interpretation for sort can use the fact that insert returns a sorted list. In fact the only non-trivial clause to prove in the whole thing is the second branch of insert, which may rely on the transitivity of (<=), and so may have to be handed off to an external theorem prover. Static contract checking/ESC yields either success, a warning that it can't prove something, with a stack trace to the condition it can't prove, or an error with a counter example and a stack trace of what goes wrong. Unannotated code is effectively inlined rather than assumed to be total, so you can mix and match this style with traditional code. The fact that you get compile time stack traces is what made me fall in love with the approach. Dana used to have (most of) a port of ghc to support SCC on darcs.haskell.org, but I don't know what happened to it. -Edward Kmett On Tue, May 4, 2010 at 2:43 PM, Gregory Crosswhite < gcross@phys.washington.edu> wrote:
I definitely like that idea. :-) Is this similar to the notion of dependent types?
Cheers, Greg
On May 4, 2010, at 11:21 AM, Kyle Murphy wrote:
This whole thing seems to be touching on something I saw recently and was quite interested in. I found a site talking about static contract checking in Haskell, unfortunately I can't seem to find it now, but this paper ( http://research.microsoft.com/en-us/um/people/simonpj/papers/verify/haskellc...) by Simon Peyton Jones among others seems to be about the same thing the site I found was talking about. The idea is to provide enough extra information to the type system to actually be able to verify that for instance, secondElement is always called with at least two items. If there exists in your code any situation in which the contract of a function could be violated (and therefore the possibility of blowing up at runtime), it no longer passes static type checking. The paper doesn't go much into the impact something like that would have on for instance GHCi, but if it was smart enough to inherit contracts from functions used and display these derived contracts this would be a very simple way to find all the edge cases of your code.
-R. Kyle Murphy -- Curiosity was framed, Ignorance killed the cat.
On Tue, May 4, 2010 at 12:56, John Lato
wrote: On Tue, May 4, 2010 at 5:31 PM, Gregory Crosswhite
wrote: Yes, but I think that it is also important to distinguish between cases
where an error is expected to be able to occur at runtime, and cases where an error could only occur at runtime *if the programmer screwed up*. If you structure your code to preserve and rely on the invariant that a given list has at least two elements, then it makes sense to call secondElement because if the list doesn't have two elements then you screwed up. Furthermore, there is no way to recover from such an error because you don't necessarily know where the invariant was broken because if you did you would have expected the possibility and already fixed it.
But hypothetically, suppose that you decided to use safeSecondElement
anyway; now you have to deal with a Nothing in your code. Since, again, you don't know how to recover from this (as if you did, you wouldn't have gotten a Nothing in the first place), the only thing you can do is propagate it through the calculation, until it reaches someone who can recover from it, which means that now your whole calculation has to be muddled up with Maybe types wrapping every result purely to capture the possibility of a bug (or hardware fault, I suppose). If your program relied on this calculation, then it *still* has no choice but to terminate, and it *still* doesn't know where the error occurred --- although if you use something like ErrorT, you might at least know what the nature of the error was. So basically, you still end up with having to terminate your program and printing out an error message reporting the existence of a bug, but now you had to add error-propagating infrastructure to your entire program to do this that makes every function more complicated, rather than relying on the built-in infrastructure supplied by Haskell in the form of undefined, error, and throwing exceptions from pure code.
If you want to make your program fault tolerant against bugs --- which
is reasonable in programs such as, say, a text editor, where inability to complete one task doesn't necessarily mean that the whole program has to stop --- then you are probably working in the IO monad and so have access to means of catching exceptions, which means that you might as well use them.
Thus, if you are dealing with invariants which only fail if you, the
programmer, screwed something up, I don't really see the benefit of using functions like safeSecondElement over secondElement. Of course, situations in which you are *expecting* subcomputations to be able to fail at runtime if, say, the input is ill-formed, are a different matter.
I agree completely, although I'm not the first to point out that catching exceptions thrown from pure code can be tricky because they might not appear where you expect them to. Of course this can also indicate your architecture needs help.
John _______________________________________________ Haskell-Cafe mailing list Haskell-Cafe@haskell.org http://www.haskell.org/mailman/listinfo/haskell-cafe
_______________________________________________ Haskell-Cafe mailing list Haskell-Cafe@haskell.org http://www.haskell.org/mailman/listinfo/haskell-cafe

This is awesome! GHC-devs , please mainline the CONTRACT pragma.
-deech
On 5/4/10, Edward Kmett
The papers are available here: http://gallium.inria.fr/~naxu/pub.html
But in general you can say things like the following:
(Dana Xu uses a slightly different notation that I can never remember).
sorted :: Ord a => [a] -> Bool
sorted [] = True sorted [x] = True sorted (x:xs) = x < head xs && sorted xs
sort :: Ord a => [a] -> { xs : [a] | sorted xs } sort [] = [] sort (x:xs) = insert x (sort xs)
insert :: Ord a => a -> { xs : [a] | sorted xs } -> { ys : [a] | sorted xs } -- insert :: Ord a => a -> { xs : [a] } -> { ys : [a] | sorted xs ==> sorted ys } insert x [] = [x] insert x yys@(y:ys) | x < y = x:yys | otherwise = y:insert x ys
And with that an abstract interpreter runs checking the partial correctness of the code. The predicates can be specified in Haskell itself, non-termination (even in the predicate) is covered by the fact that you only check partial correctness.
In the above, the abstract interpretation for sort can use the fact that insert returns a sorted list. In fact the only non-trivial clause to prove in the whole thing is the second branch of insert, which may rely on the transitivity of (<=), and so may have to be handed off to an external theorem prover.
Static contract checking/ESC yields either success, a warning that it can't prove something, with a stack trace to the condition it can't prove, or an error with a counter example and a stack trace of what goes wrong. Unannotated code is effectively inlined rather than assumed to be total, so you can mix and match this style with traditional code.
The fact that you get compile time stack traces is what made me fall in love with the approach. Dana used to have (most of) a port of ghc to support SCC on darcs.haskell.org, but I don't know what happened to it.
-Edward Kmett
On Tue, May 4, 2010 at 2:43 PM, Gregory Crosswhite < gcross@phys.washington.edu> wrote:
I definitely like that idea. :-) Is this similar to the notion of dependent types?
Cheers, Greg
On May 4, 2010, at 11:21 AM, Kyle Murphy wrote:
This whole thing seems to be touching on something I saw recently and was quite interested in. I found a site talking about static contract checking in Haskell, unfortunately I can't seem to find it now, but this paper ( http://research.microsoft.com/en-us/um/people/simonpj/papers/verify/haskellc...) by Simon Peyton Jones among others seems to be about the same thing the site I found was talking about. The idea is to provide enough extra information to the type system to actually be able to verify that for instance, secondElement is always called with at least two items. If there exists in your code any situation in which the contract of a function could be violated (and therefore the possibility of blowing up at runtime), it no longer passes static type checking. The paper doesn't go much into the impact something like that would have on for instance GHCi, but if it was smart enough to inherit contracts from functions used and display these derived contracts this would be a very simple way to find all the edge cases of your code.
-R. Kyle Murphy -- Curiosity was framed, Ignorance killed the cat.
On Tue, May 4, 2010 at 12:56, John Lato
wrote: On Tue, May 4, 2010 at 5:31 PM, Gregory Crosswhite
wrote: Yes, but I think that it is also important to distinguish between cases
where an error is expected to be able to occur at runtime, and cases where an error could only occur at runtime *if the programmer screwed up*. If you structure your code to preserve and rely on the invariant that a given list has at least two elements, then it makes sense to call secondElement because if the list doesn't have two elements then you screwed up. Furthermore, there is no way to recover from such an error because you don't necessarily know where the invariant was broken because if you did you would have expected the possibility and already fixed it.
But hypothetically, suppose that you decided to use safeSecondElement
anyway; now you have to deal with a Nothing in your code. Since, again, you don't know how to recover from this (as if you did, you wouldn't have gotten a Nothing in the first place), the only thing you can do is propagate it through the calculation, until it reaches someone who can recover from it, which means that now your whole calculation has to be muddled up with Maybe types wrapping every result purely to capture the possibility of a bug (or hardware fault, I suppose). If your program relied on this calculation, then it *still* has no choice but to terminate, and it *still* doesn't know where the error occurred --- although if you use something like ErrorT, you might at least know what the nature of the error was. So basically, you still end up with having to terminate your program and printing out an error message reporting the existence of a bug, but now you had to add error-propagating infrastructure to your entire program to do this that makes every function more complicated, rather than relying on the built-in infrastructure supplied by Haskell in the form of undefined, error, and throwing exceptions from pure code.
If you want to make your program fault tolerant against bugs --- which
is reasonable in programs such as, say, a text editor, where inability to complete one task doesn't necessarily mean that the whole program has to stop --- then you are probably working in the IO monad and so have access to means of catching exceptions, which means that you might as well use them.
Thus, if you are dealing with invariants which only fail if you, the
programmer, screwed something up, I don't really see the benefit of using functions like safeSecondElement over secondElement. Of course, situations in which you are *expecting* subcomputations to be able to fail at runtime if, say, the input is ill-formed, are a different matter.
I agree completely, although I'm not the first to point out that catching exceptions thrown from pure code can be tricky because they might not appear where you expect them to. Of course this can also indicate your architecture needs help.
John _______________________________________________ Haskell-Cafe mailing list Haskell-Cafe@haskell.org http://www.haskell.org/mailman/listinfo/haskell-cafe
_______________________________________________ Haskell-Cafe mailing list Haskell-Cafe@haskell.org http://www.haskell.org/mailman/listinfo/haskell-cafe

"aditya" == aditya siram
writes:
aditya> This is awesome! GHC-devs , please mainline the CONTRACT aditya> pragma. I think it needs a LOT more work before it is usable. (I hope I'm wrong, but Dana reckoned it needed about 7 more man-years of work.) Dana sent me a copy of her ghc 6.8 repository (which didn't compile), and I updated (by hand) a 6.11 repository. I was able to get a few test programs to be rejected as not fulfilling their contracts (due to type classes), and a few others to loop at compile time, but I couldn't find any that passed. I was supposed to have a go at debugging the loops, but never got round to it. -- Colin Adams Preston Lancashire () ascii ribbon campaign - against html e-mail /\ www.asciiribbon.org - against proprietary attachments

On May 4, 2010, at 12:31 , Gregory Crosswhite wrote:
On May 4, 2010, at 5:22 AM, John Lato wrote:
"Crashing at the point of the error" isn't necessarily useful in Haskell due to lazy evaluation. The code will crash when the result of the partial function is evaluated, which may be quite far away (in terms of function calls) from where the programmer would expect.
But hypothetically, suppose that you decided to use safeSecondElement anyway; now you have to deal with a Nothing in your code. Since, again, you don't know how to recover from this (as if you did, you wouldn't have gotten a Nothing in the first place), the only thing you can do is propagate it through the calculation, until it reaches someone who can recover from it, which means that now your whole calculation has to be muddled up with Maybe types wrapping every result purely to capture the
Using Maybe as a monad helps a lot here.
possibility of a bug (or hardware fault, I suppose). If your program relied on this calculation, then it *still* has no choice but to terminate, and it *still* doesn't know where the error occurred
It occurs to me that combining Error and Writer gives you traceback. -- brandon s. allbery [solaris,freebsd,perl,pugs,haskell] allbery@kf8nh.com system administrator [openafs,heimdal,too many hats] allbery@ece.cmu.edu electrical and computer engineering, carnegie mellon university KF8NH

Gregory Crosswhite wrote:
Yes, but I think that it is also important to distinguish between cases where an error is expected to be able to occur at runtime, and cases where an error could only occur at runtime *if the programmer screwed up*.
Well sure, but how can you demonstrate that you (the programmer) haven't screwed up? That's the point that I'm most interested in. Certainly there is a difference between exceptional behavior at runtime (e.g., due to malformed inputs) vs programming errors. We can't stop the former without broadening the scope of the typesystem to include runtime inputs; but we can, at least idealistically, stop the latter. While complex invariants require full dependent types to capture, there are a surprising number of invariants that Haskell's type system can already capture (even without GADTs!).
If you structure your code to preserve and rely on the invariant that a given list has at least two elements, then it makes sense to call secondElement because if the list doesn't have two elements then you screwed up. Furthermore, there is no way to recover from such an error because you don't necessarily know where the invariant was broken because if you did you would have expected the possibility and already fixed it.
If you're structuring your code with that invariant, then why aren't you using the correct type? data AtLeastTwo a = ALT a a [a] If your response is that you use too many of the standard list functions, then write a module with ALT versions (because evidently you need one). If your response is that you convert between lists and ALT too often, then you probably need to restructure the code. At the very least you should be using a newtype and smart constructors, so that you can actually prove your invariant whenever necessary: newtype AtLeastTwo a = Hidden_ALT [a] altEnd :: a -> a -> AtLeastTwo a altCons :: a -> AtLeastTwo a -> AtLeastTwo a altList :: a -> a -> [a] -> AtLeastTwo a fromList :: [a] -> Maybe (AtLeastTwo a) toList :: AtLeastTwo a -> [a] Without smart constructors or an ADT to enforce your invariants, why should you be convinced that you (the programmer) haven't messed up? The cheap and easy access to ADTs is one of the greatest benefits of Haskell over mainstream languages, don't fear them. -- Live well, ~wren

On May 9, 2010, at 1:04 AM, wren ng thornton wrote:
If you're structuring your code with that invariant, then why aren't you using the correct type?
I do try to use the type system as much as possible to enforce constraints. However. it is not always so simple as making sure that a list always has two elements; you might be working with some kind of complicated data structure such that if you see that it satisfies some property then if you preserved the invariant correctly then you know that a given list must have at least two elements.
If your response is that you use too many of the standard list functions, then write a module with ALT versions (because evidently you need one)
While, again, I do agree that it is smart to use the type system as much as possible to enforce constraints in order to avoid refutable pattern matches, I would argue that when you start creating duplicates of entire pre-existing modules towards this end then the cure may be starting to become worse that the disease. Cheers, Greg
participants (10)
-
aditya siram
-
Brandon S. Allbery KF8NH
-
Casey McCann
-
Colin Paul Adams
-
Edward Kmett
-
Gregory Crosswhite
-
John Lato
-
Kyle Murphy
-
Limestraël
-
wren ng thornton