10

take (-1) [] is [].

What are the reasons to prefer this over a partial function, that is, an error?

Are there use cases where this property is exploited?

false
  • 10,264
  • 13
  • 101
  • 209
  • 3
    Note: this isn't unique to haskell. For example in Python indexing outside of bounds raises an `IndexError` but slicing *never* raises an `IndexError`: whenever the slice is out of bounds it simply returns the empty list. I've been coding in python for quite a bit and I must say that this behaviour is *what you want* from a pragmatic point of view. It allows to be sloppy when slicing, making code smaller and easier and 99.9% of the time the empty list would be the correct result anyway... – Bakuriu Nov 27 '14 at 22:38

2 Answers2

7

take and drop are similar to the left-substring and right-substring functions, and it's proven in practice to be convenient for those not raise an error for negative or invalid lengths.

For example - a padding function:

pad :: Int -> String -> String
pad n str = (repeat (n - length str) ' ') ++ str

and here is a variant to pad with another string:

padWith :: String -> Int -> String -> String
padWith field n str = (take (n - length str) field) ++ str
ErikR
  • 51,541
  • 9
  • 73
  • 124
  • Mnmn, +1, in the case you give, wrapping a `(max 0 EXPR)` around does not seem to be that much of an effort. – false Nov 27 '14 at 18:27
4

Splitting a list in chunks of (at most) n pieces requires take to be total:

chunks n [] = []
chunks n xs = take n xs : chunks n (drop n xs)

Also, the current definition ensures

take n xs ++ drop n xs == xs

for any n and xs.

Arguably, we should have both takeAtMost and takeAtLeast, the latter being the partial variant (or instead returning Maybe).

A similar concern arises from zip, which is total as well, even when applied to lists of unequal length. Still, that is frequently exploited in the idiom zip [1..] xs which pairs every element of the list with its own index.

Keep however in mind that I am not arguing that a total function is always the preferred one. On many, many programming tasks obtaining a bug-revealing exception is a bliss compared with obtaining the wrong result and having no idea about where the bug is. Or even worse, getting a wrong yet plausible result, and not even discovering there is a bug.

chi
  • 111,837
  • 3
  • 133
  • 218
  • 1
    I think the OP was more interested in why `take` doesn't raise an error for negative inputs. – ErikR Nov 27 '14 at 18:19
  • I probably miss your point in your examples: It seems they always recur to `drop` which - then - would have to be defined accordingly, too – false Nov 27 '14 at 18:26
  • @false Well, yes, but it's important that `take 10 [1,2,3] == [1,2,3]` for `chunks` to work. If `take` were partial, `chunks` would be partial as well except on lengths multiples of `n`. – chi Nov 27 '14 at 18:31
  • 1
    @chi: I do not question the case of a list being too short. That is, there is the invariant, that the length of (take i l) will be smaller or equal i. Which no longer holds for negative numbers. – false Nov 27 '14 at 18:35
  • 2
    @false: I think chi's point is that `take` is partial in another sense too (taking 10 elements from the [1,2,3] is hardly more possible than taking (-1) elements). So constraining it only for negative numbers would be incoherent. By the way, Ocaml zip (called `combine`) throws when lists have different lengths, and I heard that's the pain. – Yuuri Nov 28 '14 at 10:22
  • 1
    @Yuuri Yes, that's what I meant. Still, I understand that it might be desirable in some cases to error out on negative arguments. – chi Nov 28 '14 at 12:21