
David Feuer wrote:
FWIW, I think (!!) tends to show up an awful lot (maybe mostly) in code written by beginners who aren't yet thinking of lists structurally.
Well, I have no statistical evidence, but I'd disagree. I, at least, happily use (!!) when I judge it appropriate given its time complexity and other contextual factors. In any case, indexing on lists is just one instance. Many other data structures support indexing operations, including arrays, and they are all equally partial. On what grounds should list indexing be singled out?
foldr1 is deceptive—it's easy to think it can do more than it can, and I've seen its laziness characteristics confuse even an experienced Haskeller. It's not even really nice for non-empty list types. Much clearer:
fr1 :: (a -> b -> b) -> (a -> b) -> NonEmpty a -> b
which is best matched to
data NonEmpty a = End a | Cons a (NonEmpty a)
Fine if one happens to be working with NonEmpty. But NonEmpty has its own issues, in that it is often the case that one needs to mix lists with different properties, and formally keeping track of how these properties interact at the type level is often not feasible in Haskell, and may well not be worth the effort even in languages that have much more powerful type systems, depending on objectives and practical circumstances. So again, I disagree and maintain that foldr1 is entirely reasonable.
head and tail are basic ... in Lisp.
I don't think that is a fair statement. There are many data structures beside list for which head and tail are basic operations. Look in pretty much any book on data structures and algorithms. So taking head and tail for lists as a starting point makes perfect sense from a teaching perspective, for example. (And besides, there is nothing inherently wrong with Lisp and its descendants, and well-rounded Computer Scientists and programmers should certainly be aware.)
In Haskell, they're effectively used ... *checks notes* ... in the MonadFix instance for []. Almost every good use of (!!) is for something that really should use an infinite list
data Stream a = a :< Stream a
but that uses a list for convenience.
I'd be hesitant to make such sweeping statements myself: a lot depends on the specifics and practical tradeoffs of individual cases. But in any case, again, (!!) is only but one example of an indexing operation. Why should the list one be singled out? The array one is equally partial. The fundamental point here is that there is no underlying principle here, except that undeniably Haskell's more or less "built-in" lists are used a lot and therefore they of course account for many of the errors seen. A further point is that what amounts to operational debugging annotations really have no place being mixed into types, and fundamentally undermines the idea of declarative programming. I guess I am bit of an idealist in this respect, but I still think there is a lot of mileage in approaching Haskell as a declarative language, at least until circumstances dictate that a more operational understanding is needed as a complement. Best, /Henrik foldr1 is deceptive—it's easy to think it can do more than it can, and I've seen its laziness characteristics confuse even an experienced Haskeller. It's not even really nice for non-empty list types. Much clearer: fr1 :: (a -> b -> b) -> (a -> b) -> NonEmpty a -> b This message and any attachment are intended solely for the addressee and may contain confidential information. If you have received this message in error, please contact the sender and delete the email and attachment. Any views or opinions expressed by the author of this email do not necessarily reflect the views of the University of Nottingham. Email communications with the University of Nottingham may be monitored where permitted by law.