[Haskell-cafe] ghc should be able to deduce correct use of partial functions and not give a warning in such cases

Ben newsletters at istaletan.com
Fri Apr 19 03:44:44 UTC 2024


I'm not 100% sure what Safe.tailErr function you are using (Hoogle only finds tailErr :: e -> [a] -> Either e [a] <https://hackage.haskell.org/package/errors-2.3.0/docs/Control-Error-Safe.html#v:tailErr>,  which doesn't appear compatible with the way you're using it since it takes an "error message" as first argument).

But regardless, any sort of tail function (whether "safe" or not) is going to have to work by pattern matching on the list again. null is a perfectly ordinary function that pattern matches on a list to decide whether to return True or False, and the if/then/else syntax is also essentially just special-purpose syntax for pattern matching a Bool. So your code is essentially pattern matching on a list to decide whether it's empty or not, throwing away the result of that and keeping a Bool instead, then pattern matching on the Bool to split between two cases, then pattern matching on the list again. If you had kept the result of the first pattern match there would be no need for the latter two.

Essentially the problem is "boolean blindness" (if you search online you'll find a number of articles elaborating on the concept). All being inside the else branch inherently tells you is that a Bool was False. It doesn't tell you that the list is non-empty, and so it's safe to apply tail. To know that additional information you (either the human code reader or the compiler) have to keep track of additional facts about where the Bool came from (and already know or be able to look inside the definitions of null and tail). In this trivial example this is easy work for a human, and in principle feasible for the compiler to keep track of, but it's still work. So False just isn't a very good way to encode the information "a list is non-empty".

If you instead use pattern matching, you get:

case ys of
  [] -> []
  (y:ysTail) -> [(xs, ysTail)]

Here the evidence you get out of your check when the list is non-empty isn't just a featureless False, you get evidence that the list is non-empty (and therefore has a head and a tail) by actually getting access to the head and tail. Neither you nor the compiler has to remember that False means the list isn't empty and so it's safe to extract the tail; you just have the tail!

This style of coding is generally more readable, maintainable, and less error-prone. So much so that I would opine that the compiler maintainers should specifically avoid making the compiler keep track of the provenance of Bools used in if conditions so it can suppress partiality warnings. Needing to suppress partiality warnings is usually itself a warning sign; often (not always, but often) it's an indicator that you're using simple checks where using richer evidence would work better. If you're a Haskell programmer, you should be comfortable with pattern matching as the primary tool for switching between code branches, rather than test and if, so warnings that nudge beginners away from if null as a pattern are a good thing.

(In principle the if+null+tail pattern you're using is less efficient, too. It makes 2 function calls and 3 pattern matches, where a single pattern match would suffice. I mention this later as I think the logic issues of the code are more important, and it's unlikely to be a major issue in practice; the compiler will probably inline and optimise away the extra function calls and pattern matches anyway. But it is another reason to prefer carrying the information you need from the place you checked it, rather than looking into a structure to check that what you need is present and then later looking into the structure again to actually extract what you need. In more complex cases it could be a genuine efficiency concern.)

Cheers,
Ben


18 Apr 2024, 10:24 pm by george.colpitts at gmail.com:

> It seems that I can replace 
>
>
>>  if null ys then [] else [(xs,tail ys)])
>>
>>
> with 
>
>
>> if null ys then [] else [(xs, Safe.tailErr ys)])
>>
>
> and not get any warnings so I think I am going to do that. I'm surprised that the warning doesn't mention that option to fix the issue. Do you see any issues with this fix?
>
> Thanks,
> George
>
>
> On Tue, Apr 16, 2024 at 11:57 AM George Colpitts <> george.colpitts at gmail.com> > wrote:
>
>> Hi Henning,
>>
>> Thanks for the quick response!
>> Yes, that's basically how I fixed it but I really don't want to have to do that since the code is correct. Not a big deal but may be irritating to beginners or large projects that want to eliminate warnings. Do you think an ER would be rejected ? If I remember correctly there is already detection of incomplete pattern matching that is ok and in such cases warnings are omitted. This would be similar to that.
>>
>> Cheers,
>> George
>>
>>
>> On Tue, Apr 16, 2024 at 11:46 AM Henning Thielemann <>> lemming at henning-thielemann.de>> > wrote:
>>
>>>
>>> On Tue, 16 Apr 2024, George Colpitts wrote:
>>>  
>>>  > ghc should be able to deduce correct use of partial functions and not give a warning in such cases, e.g.
>>>  > in 9.8.2
>>>  > 
>>>  >      if null ys then [] else [(xs,tail ys)])
>>>  > 
>>>  > gets a warning
>>>  > 
>>>  >       warning: [GHC-63394] [-Wx-partial]
>>>  > 
>>>  > but it is clear that this use of tail will never be a problem so IMHO that line of code should not get a
>>>  > warning. 
>>>  > 
>>>  > Does anybody know if there is a plan or enhancement request to eliminate such warnings? 
>>>  
>>>  
>>>  I would just use good old pattern matching:
>>>  
>>>  case ys of
>>>      [] -> []
>>>      _:zs -> [(xs,zs)]
>>>

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.haskell.org/pipermail/haskell-cafe/attachments/20240419/789019a5/attachment.html>


More information about the Haskell-Cafe mailing list