`Data.List.NonEmpty.init` and `last`

I was looking at the partial functions Data.List.init and Data.List.last and their total namesakes in Data.List.NonEmpty, and I noticed that the latter were specified as:

-- | Extract the last element of the stream.
last :: NonEmpty a -> a
last ~(a :| as) = List.last (a : as)

-- | Extract everything except the last element of the stream.
init :: NonEmpty a -> [a]
init ~(a :| as) = List.init (a: as)

I wondered what were the respective merits or drawbacks of recursive specifications, such as:

last :: NonEmpty a -> a
last (a :| []) = a
last (_ :| (a : as)) = last (a :| as)

init :: NonEmpty a -> [a]
init (_ :| []) = []
init (a1 :| (a2 : as)) = a1 : init (a2 :| as)
1 Like

Your recursive version should be better. The existing definition delays forcing of the input with ~, but this is pointless, because List.last is not lazy.

If anyone wonders how to avoid manual recursion:

import qualified Data.Foldable1 as F1

init :: NonEmpty a -> [a]
init = F1.foldrMap1 (const []) (:)

last :: NonEmpty a -> a
last = F1.foldrMap1 id (const id)
2 Likes

That is neat. If Data.List.NonEmpty.last were to be specified using a method of the Foldable1 class, which also promises last, should the NonEmpty instance of Foldable1 define its last method, rather than being equated to Data.List.NonEmpty.last? So:

-- module Data.Foldable1
instance Foldable1 NonEmpty where
    ...
    head = NE.head
    last = foldrMap1 id (const id)

-- module Data.List.NonEmpty
import qualified Data.Foldable1 as F1

last :: NonEmpty a -> a
last = F1.last

At the moment Data.Foldable1 lives in base and eventually depends on NonEmpty from GHC.Internal.Base which is in ghc-internal. Swapping the dependency order is likely to be complicated and not worth it.

By way of update, this prompted me to raise a corresponding issue with the Core Libraries Committee: Don't specify `Data.List.NonEmpty.{init,last} in terms of partial `... .List.{init,last}` · Issue #293 · haskell/core-libraries-committee · GitHub