{-# LANGUAGE Safe #-} {- | Module : Relude.Enum Copyright : (c) 2021-2023 Kowainik SPDX-License-Identifier : MIT Maintainer : Kowainik <xrom.xkov@gmail.com> Stability : Stable Portability : Portable Reexports 'Enum' related typeclasses and functions. Also introduces a few useful helpers to work with Enums. __Note:__ 'universe', 'universeNonEmpty' and 'inverseMap' were previously in the extra modules, but due to their benefit in different use cases. If you imported @Relude.Extra.Enum@ module, you can remove it now, as these functions are reexported in the main "Relude" module. @since 1.0.0.0 -} module Relude.Enum ( -- * Useful combinators for Enums universe , universeNonEmpty , inverseMap -- * Base reexports , module GHC.Enum ) where import GHC.Enum (Bounded (..), Enum (..), boundedEnumFrom, boundedEnumFromThen) import Data.List.NonEmpty (NonEmpty (..)) import Data.List (drop) import Data.Maybe (Maybe (..)) import Relude.Base (Ord (..)) import Relude.Extra.Tuple (fmapToFst) import qualified Data.Map.Strict as M -- $setup -- >>> import Relude {- | Returns all values of some 'Bounded' 'Enum' in ascending order. >>> universe :: [Bool] [False,True] >>> universe @Ordering [LT,EQ,GT] >>> data TrafficLight = Red | Blue | Green deriving (Show, Enum, Bounded) >>> universe :: [TrafficLight] [Red,Blue,Green] >>> data Singleton = Singleton deriving (Show, Enum, Bounded) >>> universe @Singleton [Singleton] @since 0.1.0 -} universe :: (Bounded a, Enum a) => [a] universe :: forall a. (Bounded a, Enum a) => [a] universe = [forall a. Bounded a => a minBound .. forall a. Bounded a => a maxBound] {-# INLINE universe #-} {- | Like 'universe', but returns 'NonEmpty' list of some enumeration >>> universeNonEmpty :: NonEmpty Bool False :| [True] >>> universeNonEmpty @Ordering LT :| [EQ,GT] >>> data TrafficLight = Red | Blue | Green deriving (Show, Eq, Enum, Bounded) >>> universeNonEmpty :: NonEmpty TrafficLight Red :| [Blue,Green] >>> data Singleton = Singleton deriving (Show, Eq, Enum, Bounded) >>> universeNonEmpty @Singleton Singleton :| [] @since 0.7.0.0 -} universeNonEmpty :: forall a . (Bounded a, Enum a) => NonEmpty a universeNonEmpty :: forall a. (Bounded a, Enum a) => NonEmpty a universeNonEmpty = forall a. Bounded a => a minBound forall a. a -> [a] -> NonEmpty a :| forall a. Int -> [a] -> [a] drop Int 1 forall a. (Bounded a, Enum a) => [a] universe {-# INLINE universeNonEmpty #-} {- | @inverseMap f@ creates a function that is the inverse of a given function @f@. It does so by constructing 'M.Map' internally for each value @f a@. The implementation makes sure that the 'M.Map' is constructed only once and then shared for every call. __Memory usage note:__ don't inverse functions that have types like 'Int' as their input. In this case the created 'M.Map' will have huge size. The complexity of reversed mapping is \(\mathcal{O}(\log n)\). __Performance note:__ make sure to specialize monomorphic type of your functions that use 'inverseMap' to avoid 'M.Map' reconstruction. One of the common 'inverseMap' use-case is inverting the 'show' or a 'show'-like function. >>> data Color = Red | Green | Blue deriving (Show, Enum, Bounded) >>> parse = inverseMap show :: String -> Maybe Color >>> parse "Red" Just Red >>> parse "Black" Nothing __Correctness note:__ 'inverseMap' expects /injective function/ as its argument, i.e. the function must map distinct arguments to distinct values. Typical usage of this function looks like this: @ __data__ GhcVer = Ghc802 | Ghc822 | Ghc844 | Ghc865 | Ghc881 __deriving__ ('Eq', 'Ord', 'Show', 'Enum', 'Bounded') showGhcVer :: GhcVer -> 'Text' showGhcVer = \\__case__ Ghc802 -> "8.0.2" Ghc822 -> "8.2.2" Ghc844 -> "8.4.4" Ghc865 -> "8.6.5" Ghc881 -> "8.8.1" parseGhcVer :: 'Text' -> 'Maybe' GhcVer parseGhcVer = 'inverseMap' showGhcVer @ @since 0.1.1 -} inverseMap :: forall a k . (Bounded a, Enum a, Ord k) => (a -> k) -> (k -> Maybe a) inverseMap :: forall a k. (Bounded a, Enum a, Ord k) => (a -> k) -> k -> Maybe a inverseMap a -> k f = \k k -> forall k a. Ord k => k -> Map k a -> Maybe a M.lookup k k Map k a dict where dict :: M.Map k a dict :: Map k a dict = forall k a. Ord k => [(k, a)] -> Map k a M.fromList (forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f (b, a) fmapToFst a -> k f (forall a. (Bounded a, Enum a) => [a] universe @a)) {-# INLINE inverseMap #-}