Reputation: 96
I'm driven to ask this question after several years of littering libraries with definitions like this:
fragileZip :: [t1] -> [t2] -> [(t1, t2)]
fragileZip a b = loop a b
where
loop [] [] = []
loop (h1:t1) (h2:t2) = (h1,h2) : loop t1 t2
loop _ _ = error "fragileZip: lists were not the same length."
My experience is that the decisions made in the Haskell prelude -- regarding take
, drop
, zip
, etc not erroring upon insufficient elements -- allow cute tricks with infinite lists, and can be convenient in certain circumstances, but are devious sources of silent failures, when, for example, zipping lists that are expected to be the same length.
Does anybody know of a full Data.List
replacement that tries to be "fragile" like the above zip, always throwing an error if expectations are not met: if list lengths don't match, or if there are not enough elements?
[Footnotes: yes, it would be good to program with total functions, but that means discharging the relevant proof obligations with type-level information, not masking errors by returning bad values! Standard zip quietly dropping data can be just such a silent failure. Also, I don't care how the error results are communicated (exception, Maybe, or Either). I'll take anything if there is an existing hackage lib that fits the bill!]
Upvotes: 1
Views: 241
Reputation: 27766
The safe
package contains "safer" versions of many functions from the Prelude
; they do not throw errors and instead return Maybe
values on failure. You will still be detecting the error at runtime, though.
The errors
package re-exports the functions from safe
and adds other convenience functions for error-handling.
If you are willing to dive into the newer, dependent-type like features of Haskell, the sized-vector
package offers list whose size is checked at compile-time. In particular, there's the zipSame
function that only zips lists with the same length:
{-# LANGUAGE DataKinds #-}
import Data.Vector.Sized
import Data.Type.Natural
vec2 :: Vector Char Two
vec2 = 'a' :- 'b' :- Nil
vec3 :: Vector Char Three
vec3 = 'x' :- 'y' :- 'z' :- Nil
vecZipped = zipSame vec2 vec3
This code fails at compile time with the error:
Couldn't match type 'S Zero with 'Z
Expected type: Vector Char Two
Actual type: Vector Char Three
In the second argument of `zipSame', namely `vec3'
In the expression: zipSame vec2 vec3
In an equation for `vecZipped': vecZipped = zipSame vec2 vec3
See here for a tutorial for sized-vector
and dependent types on Haskell. The comments also have useful information.
(Sometimes zipping lists of different sizes is what you want, however. I often zip finite lists with infinite ones, because the latter are easier to define.)
Upvotes: 4
Reputation: 34398
Your zip can be found as pair
in the list-extras package. There is also the safe package, though it addresses head
, tail
and their error-throwing ilk, rather than zip
, drop
, etc.
Upvotes: 1
Reputation: 48766
I think what you are asking goes against the philosophy of Haskell. Code involving error
or any other runtime crashes isn't good for valid reasons.
But obviously, you can create a wrapper around itself if you want:
fragileZip :: [a] -> [b] -> [(a,b)]
fragileZip x y = if length x == length y
then zip x y
else error "Not of the same length"
If you really want to showcase some error in the result type, then use the type Maybe
or Either
for that. Example:
fragileZip :: [a] -> [b] -> Maybe [(a,b)]
fragileZip x y = if length x == length y
then Just $ zip x y
else Nothing
Upvotes: 3