{-# LANGUAGE ConstraintKinds #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE Safe #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeOperators #-} {-# LANGUAGE UndecidableInstances #-} {- | Module : Relude.List.NonEmpty Copyright : (c) 2016 Stephen Diehl (c) 2016-2018 Serokell (c) 2018-2022 Kowainik SPDX-License-Identifier : MIT Maintainer : Kowainik Stability : Stable Portability : Portable This module contains reexports from "Data.List.NonEmpty" and safe functions to work with list type in terms of 'NonEmpty'. Note, that "Relude" reexports 'head', 'tail', 'init', 'last' from "Data.List.NonEmpty" instead of the "Data.List", so these functions are safe to use. +------------+--------------+-----------------------+ | | @base@ | @relude@ | +============+==============+=======================+ | __'head'__ | @[a] -> a@ | @'NonEmpty' a -> a@ | +------------+--------------+-----------------------+ | __'tail'__ | @[a] -> [a]@ | @'NonEmpty' a -> [a]@ | +------------+--------------+-----------------------+ | __'last'__ | @[a] -> a@ | @'NonEmpty' a -> a@ | +------------+--------------+-----------------------+ | __'init'__ | @[a] -> [a]@ | @'NonEmpty' a -> [a]@ | +------------+--------------+-----------------------+ @relude@ also provides custom type error for better experience with transition from lists to 'NonEmpty' with those functions. Let's examine the behaviour of the @relude@ list functions comparing to the corresponding @base@ one on the example of the 'head' function: +-----------------+-----------------------------------------+ | | 'head' | +=================+=========================================+ | __@base@__ | @[a] -> a@ | +-----------------+-----------------------------------------+ | __@relude@__ | @'NonEmpty' a -> a@ | +-----------------+-----------------------------------------+ | Example | @> 'Data.List.head' [1..5]@ | | with list +-----------------------------------------+ | __@base@__ | @1@ | +-----------------+-----------------------------------------+ | Example | @> 'Data.List.head' []@ | | with empty list +-----------------------------------------+ | __@base@__ |@*** Exception: Prelude.head: empty list@| +-----------------+-----------------------------------------+ | Example |@> 'head' $ 1 :| [2..5]@ | | with 'NonEmpty' +-----------------------------------------+ | __@relude@__ | @1@ | +-----------------+-----------------------------------------+ | Example | @> 'viaNonEmpty' 'head' [1..5]@ | | with list +-----------------------------------------+ | __@relude@__ | @'Just' 1@ | +-----------------+-----------------------------------------+ | Example |@> 'viaNonEmpty' 'head' []@ | | with empty list +-----------------------------------------+ | __@relude@__ | @'Nothing'@ | +-----------------+-----------------------------------------+ @since 0.2.0 -} module Relude.List.NonEmpty ( -- * Reexports from "DataList.NonEmpty" NonEmpty (..) , nonEmpty , head , tail , last , init -- * Combinators , viaNonEmpty , whenNotNull , whenNotNullM ) where import Data.List.NonEmpty (NonEmpty (..), nonEmpty) import GHC.TypeLits (ErrorMessage (..), Symbol, TypeError) import Relude.Applicative (Applicative, pass) import Relude.Base (Constraint, Type) import Relude.Function ((.)) import Relude.Functor (fmap) import Relude.Monad (Maybe (..), Monad (..)) import qualified Data.List.NonEmpty as NE (head, init, last, tail) -- $setup -- >>> import Relude {- | For safe work with lists using functions for 'NonEmpty'. >>> viaNonEmpty head [1] Just 1 >>> viaNonEmpty head [] Nothing @since 0.1.0 -} viaNonEmpty :: (NonEmpty a -> b) -> [a] -> Maybe b viaNonEmpty f = fmap f . nonEmpty {-# INLINE viaNonEmpty #-} {- | Performs given action over 'NonEmpty' list if given list is non empty. >>> whenNotNull [] $ \(b :| _) -> print (not b) >>> whenNotNull [False,True] $ \(b :| _) -> print (not b) True -} whenNotNull :: Applicative f => [a] -> (NonEmpty a -> f ()) -> f () whenNotNull [] _ = pass whenNotNull (x:xs) f = f (x :| xs) {-# INLINE whenNotNull #-} -- | Monadic version of 'whenNotNull'. whenNotNullM :: Monad m => m [a] -> (NonEmpty a -> m ()) -> m () whenNotNullM ml f = ml >>= \l -> whenNotNull l f {-# INLINE whenNotNullM #-} -- | For tracking usage of ordinary list with @head@-like functions. type IsNonEmpty (f :: Type -> Type) -- Container, e.g. NonEmpty or [] (a :: Type) -- Type of the element (res :: Type) -- Type of the result of the work of the function (fun :: Symbol) -- Function name = (f ~ NonEmpty, CheckNonEmpty f a res fun) type family CheckNonEmpty (f :: Type -> Type) (a :: Type) (res :: Type) (fun :: Symbol) :: Constraint where CheckNonEmpty NonEmpty _ _ _ = () CheckNonEmpty [] a res fun = TypeError ( 'Text "'" ':<>: 'Text fun ':<>: 'Text "' works with 'NonEmpty', not ordinary lists." ':$$: 'Text "Possible fix:" ':$$: 'Text " Replace: [" ':<>: 'ShowType a ':<>: 'Text "]" ':$$: 'Text " With: NonEmpty " ':<>: 'ShowType a ':$$: 'Text "" ':$$: 'Text "However, you can use '" ':<>: 'Text fun ':<>: 'Text "' with the ordinary lists." ':$$: 'Text "Apply 'viaNonEmpty' function from relude:" ':$$: 'Text " viaNonEmpty " ':<>: 'Text fun ':<>: 'Text " (yourList)" ':$$: 'Text "Note, that this will return 'Maybe " ':<>: 'ShowType res ':<>: 'Text "'" ':$$: 'Text "therefore it is a safe function unlike '" ':<>: 'Text fun ':<>: 'Text "' from the standard Prelude" ) CheckNonEmpty t a _ fun = TypeError ( 'Text "'" ':<>: 'Text fun ':<>: 'Text "' works with 'NonEmpty " ':<>: 'ShowType a ':<>: 'Text "' lists" ':$$: 'Text "But given: " ':<>: 'ShowType t ':<>: 'Text " " ':<>: 'ShowType a ) {- | @O(1)@. Extracts the first element of a 'NonEmpty' list. Actual type of this function is the following: @ head :: 'NonEmpty' a -> a @ but it was given a more complex type to provide friendlier compile time errors. >>> head ('a' :| "bcde") 'a' >>> head [0..5 :: Int] ... ... 'head' works with 'NonEmpty', not ordinary lists. Possible fix: Replace: [Int] With: NonEmpty Int ... However, you can use 'head' with the ordinary lists. Apply 'viaNonEmpty' function from relude: viaNonEmpty head (yourList) Note, that this will return 'Maybe Int' therefore it is a safe function unlike 'head' from the standard Prelude ... >>> head (Just 'a') ... ... 'head' works with 'NonEmpty Char' lists But given: Maybe Char ... -} head :: IsNonEmpty f a a "head" => f a -> a head = NE.head {-# INLINE head #-} {- | @O(n)@. Return all the elements of a 'NonEmpty' list except the last one element. Actual type of this function is the following: @ init :: 'NonEmpty' a -> [a] @ but it was given a more complex type to provide friendlier compile time errors. >>> init ('a' :| "bcde") "abcd" >>> init [0..5 :: Int] ... ... 'init' works with 'NonEmpty', not ordinary lists. Possible fix: Replace: [Int] With: NonEmpty Int ... However, you can use 'init' with the ordinary lists. Apply 'viaNonEmpty' function from relude: viaNonEmpty init (yourList) Note, that this will return 'Maybe [Int]' therefore it is a safe function unlike 'init' from the standard Prelude ... >>> init (Just 'a') ... ... 'init' works with 'NonEmpty Char' lists But given: Maybe Char ... -} init :: IsNonEmpty f a [a] "init" => f a -> [a] init = NE.init {-# INLINE init #-} {- | @O(n)@. Extracts the last element of a 'NonEmpty' list. Actual type of this function is the following: @ last :: 'NonEmpty' a -> a @ but it was given a more complex type to provide friendlier compile time errors. >>> last ('a' :| "bcde") 'e' >>> last [0..5 :: Int] ... ... 'last' works with 'NonEmpty', not ordinary lists. Possible fix: Replace: [Int] With: NonEmpty Int ... However, you can use 'last' with the ordinary lists. Apply 'viaNonEmpty' function from relude: viaNonEmpty last (yourList) Note, that this will return 'Maybe Int' therefore it is a safe function unlike 'last' from the standard Prelude ... >>> last (Just 'a') ... ... 'last' works with 'NonEmpty Char' lists But given: Maybe Char ... -} last :: IsNonEmpty f a a "last" => f a -> a last = NE.last {-# INLINE last #-} {- | @O(1)@. Return all the elements of a 'NonEmpty' list after the head element. Actual type of this function is the following: @ tail :: 'NonEmpty' a -> [a] @ but it was given a more complex type to provide friendlier compile time errors. >>> tail ('a' :| "bcde") "bcde" >>> tail [0..5 :: Int] ... ... 'tail' works with 'NonEmpty', not ordinary lists. Possible fix: Replace: [Int] With: NonEmpty Int ... However, you can use 'tail' with the ordinary lists. Apply 'viaNonEmpty' function from relude: viaNonEmpty tail (yourList) Note, that this will return 'Maybe [Int]' therefore it is a safe function unlike 'tail' from the standard Prelude ... >>> tail (Just 'a') ... ... 'tail' works with 'NonEmpty Char' lists But given: Maybe Char ... -} tail :: IsNonEmpty f a [a] "tail" => f a -> [a] tail = NE.tail {-# INLINE tail #-}