haskellhaskell-prelude

Using alternative preludes in haskell


I'm interested in alternative Preludes. I understand there are many choices:

  1. https://hackage.haskell.org/packages/#cat:Prelude
  2. https://guide.aelve.com/haskell/alternative-preludes-zr69k1hc

I understand one simple thing a lot of them fix is text, and another is in functions like head that error pretty hard when you might prefer they are safer.

However, when I try to use these alternatives, the behavior in head, hmm, just seems to break the function completely, and doesn't look like an improvement to me. Here are some examples:

Prelude

Prelude> head [1]
1
Prelude> head []
*** Exception: Prelude.head: empty list

Foundation

Foundation> head [1]

<interactive>:6:6: error:
    • Couldn't match expected type ‘NonEmpty c’
                  with actual type ‘[Integer]’
    • In the first argument of ‘head’, namely ‘[1]’
      In the expression: head [1]
      In an equation for ‘it’: it = head [1]
    • Relevant bindings include
        it :: foundation-0.0.21:Foundation.Collection.Element.Element c
          (bound at <interactive>:6:1)
Foundation> head []

<interactive>:7:6: error:
    • Couldn't match expected type ‘NonEmpty c’ with actual type ‘[a0]’
    • In the first argument of ‘head’, namely ‘[]’
      In the expression: head []
      In an equation for ‘it’: it = head []
    • Relevant bindings include
        it :: foundation-0.0.21:Foundation.Collection.Element.Element c
          (bound at <interactive>:7:1)

Safe

Safe> head []

<interactive>:22:1: error: Variable not in scope: head :: [a0] -> t

Classy Prelude

ClassyPrelude> head [1]

<interactive>:24:6: error:
    • Couldn't match expected type ‘NonNull mono’
                  with actual type ‘[Integer]’
    • In the first argument of ‘head’, namely ‘[1]’
      In the expression: head [1]
      In an equation for ‘it’: it = head [1]
    • Relevant bindings include
        it :: Element mono (bound at <interactive>:24:1)

Relude

Relude> head [1]

<interactive>:27:6: error:
    • Couldn't match expected type ‘NonEmpty a’
                  with actual type ‘[Integer]’
    • In the first argument of ‘head’, namely ‘[1]’
      In the expression: head [1]
      In an equation for ‘it’: it = head [1]
    • Relevant bindings include it :: a (bound at <interactive>:27:1)

Rio

RIO> head [1]

<interactive>:7:1: error:
    Variable not in scope: head :: [Integer] -> t

Protolude

Protolude> head [1]
Just 1
Protolude> head []
Nothing

This looks good---it also works for tail, right?

Protolude> tail [1]

<interactive>:12:1: error:
    • Variable not in scope: tail :: [Integer] -> t
    • Perhaps you meant ‘tails’ (imported from Protolude)

Protolude> tails [1]
[[1],[]]

Protolude> tails []
[[]]

Well, that's not exactly a drop-in replacement.

What am I missing in why this is better, why these functions have been defined if they're just going to fail?


Solution

  • In most cases, they are being introduced because they fail at compile time instead of runtime.

    The problem with Prelude.head is not (only) that it can fail. It is that it has to, since there is no way to take a list [a] and always produce an element a, since the input list might be empty. There is no easy fix that is a drop-in replacement, a radical change is needed.

    A safer, and arguably better prelude can address this issue in one of the following ways:

    In any case, the programmer has to modify the code. There's no way around it. However, once the compile time errors are resolved, the program is guaranteed to never produce head: empty list errors at runtime.