Description
Haddocks for Data.List
(technically, GHC.List
) warn against head
and tail
on the ground of their partiality.
I propose to promote these warnings to the pragma level as per MR !9290:
{-# WARNING head "This is a partial function, it throws an error on empty lists. Use pattern matching or Data.List.uncons instead. Consider refactoring to use Data.List.NonEmpty." #-}
{-# WARNING tail "This is a partial function, it throws an error on empty lists. Replace it with drop 1, or use pattern matching or Data.List.uncons instead. Consider refactoring to use Data.List.NonEmpty." #-}
I do not propose any further steps such as deprecation or removal of these functions. This is deliberately as conservative as possible. See #70 for a wider discussion of a wider proposal.
Why only head
and tail
? Because these are functions, for which the widest range of replacements exist, almost always allowing for a safe, concise and local fix (see examples below). E. g., for init
/ last
there is currently no such replacement (one must push for addition of Data.List.unsnoc
first), and things like !!
and maximum
are even worse.
Why {-# WARNING #-}
and not {-# DEPRECATED #-}
? Because deprecation implies a future removal, and ambitions of this proposal are much smaller. It's already enshrined in base
that these functions deserve a warning, we just promote its visibility, which should be less controversial.
The impact of the change is that users of head
and tail
will receive a GHC warning message. This is not an error and does not prevent from compilation, thus is not a breaking change. Users are recommended to follow the suggestion, or disable -Wno-warnings-deprecations
(which is a sensible thing to do, for example, in a test suite), but they are also free to do nothing at all. Old packages will continue to work.
To avoid any confusion, -Wno-warnings-deprecations
suppresses {-# WARNING #-}
and {-# DEPRECATED #-}
, but not any other GHC warnings. Those, who enabled -Werror
, can pass -Wwarn=warnings-deprecations
to downgrade this particular group back from errors to warnings. GHCi users can put :set -Wno-warnings-deprecations
into their .ghci
config.
There is a concern that -Wno-warnings-deprecations
disables all {-# WARNING #-}
and {-# DEPRECATED #-}
, whatever the source. However, current Haskell ecosystem rarely makes much use of them, so I believe it is still a palatable compromise between seeing no warnings and making no changes.
Hardcore fans of head
and tail
, who are not satisfied with disabling warnings, are welcome to create a local file or even release a package, providing, say, Data.List.Partial
, containing original definitions of head
and tail
without {-# WARNING #-}
. I'm however opposed to introducing such Data.List.Partial
into base
itself: we won't be able to root it out ever.
GHC proposals ghc-proposals/ghc-proposals#454 and ghc-proposals/ghc-proposals#541 propose extensions to GHC warnings mechanism. Unfortunately, neither of them is approved or has a committed implementor, and this status does not seem to change soon, so it would be wrong to speculate on their precise nature. If and when they become a part of GHC, one can indeed ask for a review of {-# WARNING #-}
pragmas.
How would you rewrite instance MonadFix []
without head
and tail
?
instance MonadFix [] where
mfix f = case fix (f . head) of
[] -> []
(x:_) -> x : mfix (tail . f)
I'd rewrite it this way:
instance MonadFix [] where
mfix f = case fix (take 1 >=> f) of
[] -> []
(x:_) -> x : mfix (drop 1 . f)
How would you rewrite this snippet?
case product xs of
1 -> foo
n -> bar n (head xs)
Besides options in #87 (comment), one can do this:
case (xs, product xs) of
([], _) -> foo
(_, 1) -> foo
(x : _, n) -> bar n x
or (if you insist on exactly two clauses):
case xs of
x : _ | n <- product xs, n /= 1 = bar n x
_ -> foo
How would you rewrite this snippet?
head $ filter (`notElem` hashes) $ map showt [0::Int ..]
I'd use a proper library for infinite lists aka streams: Stream
, streams
or infinite-list
. E. g., Stream
provides total head :: Stream a -> a
and filter :: Stream a -> Stream a
, so the snippet can be rewritten in a total way as
import Data.Stream as S
S.head $ S.filter (`notElem` hashes) $ S.map showt $ S.iterate (+1) (0 :: Int)
infinite-list
can make it even neater offering (0...)
syntax to replace [0..]
.