content: {-# LANGUAGE Safe #-}
  {-# LANGUAGE BangPatterns #-}
  
  {- |
  Module                  : Relude.Extra.Foldable
  Copyright               : (c) 2018-2023 Kowainik
  SPDX-License-Identifier : MIT
  Maintainer              : Kowainik <xrom.xkov@gmail.com>
  Stability               : Stable
  Portability             : Portable
  
  Contains utility functions for working with tuples.
  
  @since 0.6.0.0
  -}
  
  module Relude.Extra.Foldable
      ( foldlSC
      , average
      ) where
  
  import Relude
  
  
  -- $setup
  
  {- | A left-associative fold that's tail-recursive but can still short-circuit.
  Returning a 'Left' short-circuits and immediately returns the value inside.
  Returning a 'Right' continues the fold as usual with the value inside.
  
  >>> foldlSC (\acc x -> if x == 0 then Left 0 else Right $! acc * x) 1 [1..6]
  720
  >>> foldlSC (\acc x -> if x == 0 then Left 0 else Right $! acc * x) 1 (0:error "Short-circuiting should keep this from happening")
  0
  
  @since 0.6.0.0
  -}
  foldlSC :: forall t b a. Foldable t => (b -> a -> Either b b) -> b -> t a -> b
  foldlSC f = flip $ foldr go id
    where
      go :: a -> (b -> b) -> b -> b
      go x k z = case f z x of
          Left l  -> l
          Right r -> k r
  {-# INLINE foldlSC #-}
  
  {- | Given a 'Foldable' of 'Fractional' elements, computes the average if
  possible and returns 'Maybe' element.
  
  >>> average [42]
  Just 42.0
  >>> average @Double [1, 2, 3, 4]
  Just 2.5
  >>> average @Float [1.5, 2.5, 3 ,4]
  Just 2.75
  >>> average []
  Nothing
  
  @since 1.0.0.0
  -}
  average :: forall a f . (Foldable f, Fractional a) => f a -> Maybe a
  average xs
      | null xs = Nothing
      | otherwise = Just
          . uncurry (/)
          . foldl' (\(!total, !count) x -> (total + x, count + 1)) (0,0)
          $ xs
  {-# INLINE average #-}
  