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.