RyanN
RyanN

Reputation: 96

Where is a mature library of "fragile" list operations for Haskell?

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

Answers (3)

danidiaz
danidiaz

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

duplode
duplode

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

Sibi
Sibi

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

Related Questions