Skip to content

Commit

Permalink
[#270] Standardise some Extra API (#360)
Browse files Browse the repository at this point in the history
* [#270] Standardise some Extra API

Resolves #270

* Apply suggestions from code review

Co-authored-by: Dmitrii Kovanikov <kovanikov@gmail.com>

* Fix Monoid build on GHC versions

Co-authored-by: Dmitrii Kovanikov <kovanikov@gmail.com>
  • Loading branch information
vrom911 and chshersh committed Mar 11, 2021
1 parent 60995b9 commit 1e5fb60
Show file tree
Hide file tree
Showing 10 changed files with 176 additions and 120 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,14 @@ The changelog is available [on GitHub][2].
Reimplement `ordNub` through `nubOrd` from `containers`.

Add `intNub` and `intNubOn` functions.
* [#270](https://github.com/kowainik/relude/issues/270):
Standardise `universe`, `universeNonEmpty` and `inverseMap` functions that
previously were introduced in the `Relude.Extra.Enum` module. `Relude.Enum`
module created that is exported in the main `Relude` module by default.

__Migration guide:__ If you were using any of these functions you can now
remove `Relude.Extra.Enum` from your imports and explicit `mixins` section
as they are available for you with the `Relude` module.
* [#347](https://github.com/kowainik/relude/issues/347):
Add `ordNubOn` function.
* [#268](https://github.com/kowainik/relude/issues/268):
Expand Down
1 change: 1 addition & 0 deletions relude.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ library
Relude.Container.Reexport
Relude.Debug
Relude.DeepSeq
Relude.Enum
Relude.Exception
Relude.File
Relude.Foldable
Expand Down
8 changes: 8 additions & 0 deletions src/Relude.hs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@ module Relude
-- $debug
, module Relude.DeepSeq
-- $deepseq
, module Relude.Enum
-- $enum
, module Relude.Exception
-- $exception
, module Relude.File
Expand Down Expand Up @@ -131,6 +133,7 @@ import Relude.Bool
import Relude.Container
import Relude.Debug
import Relude.DeepSeq
import Relude.Enum
import Relude.Exception
import Relude.File
import Relude.Foldable
Expand Down Expand Up @@ -175,6 +178,11 @@ __"Relude.DeepSeq"__ has reexports from "Control.DeepSeq" module and functions
to evaluate expressions to weak-head normal form or normal form.
-}

{- $enum
__"Relude.Enum"__ reexports 'Enum' related typeclasses and functions. Also
introduced a few useful helpers to work with Enums.
-}

{- $exception
__"Relude.Exception"__ contains reexports from "Control.Exception", introduces
'bug' function as better 'error' and 'Exc' pattern synonym for convenient
Expand Down
2 changes: 0 additions & 2 deletions src/Relude/Base.hs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ module Relude.Base

-- * Basic type classes
, module GHC.Base
, module GHC.Enum
, module GHC.Generics
, module GHC.Show

Expand Down Expand Up @@ -62,7 +61,6 @@ import Data.Typeable (Typeable)
import Data.Void (Void, absurd, vacuous)

import GHC.Base (asTypeOf, ord, seq, ($!))
import GHC.Enum (Bounded (..), Enum (..), boundedEnumFrom, boundedEnumFromThen)
import GHC.Generics (Generic)
import GHC.Show (Show)

Expand Down
4 changes: 2 additions & 2 deletions src/Relude/Debug.hs
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,8 @@ import GHC.Exts (RuntimeRep, TYPE)
import GHC.TypeLits (ErrorMessage (..), TypeError)

import Relude.Applicative (Applicative)
import Relude.Base (Bounded, Char, Constraint, Enum, Eq, Generic, HasCallStack, Ord, Show, Type,
Typeable)
import Relude.Base (Char, Constraint, Eq, Generic, HasCallStack, Ord, Show, Type, Typeable)
import Relude.Enum (Bounded, Enum)
import Relude.String (Read, String, Text, toString)

import qualified Debug.Trace as Debug
Expand Down
146 changes: 146 additions & 0 deletions src/Relude/Enum.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
{- |
Copyright: (c) 2021 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 x.x.x.x
-}

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 = [minBound .. 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 = minBound :| drop 1 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 result. 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 f = \k -> M.lookup k dict
where
dict :: M.Map k a
dict = M.fromList (fmapToFst f (universe @a))
{-# INLINE inverseMap #-}
114 changes: 1 addition & 113 deletions src/Relude/Extra/Enum.hs
Original file line number Diff line number Diff line change
Expand Up @@ -13,125 +13,13 @@ Mini @bounded-enum@ framework inside @relude@.
-}

module Relude.Extra.Enum
( universe
, universeNonEmpty
, inverseMap
, next
( next
, prev
, safeToEnum
) where

import Relude
import Relude.Extra.Tuple (fmapToFst)

import qualified Data.Map.Strict as M


{- | 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 = [minBound .. 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 = minBound :| drop 1 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 result. 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 f = \k -> M.lookup k dict
where
dict :: M.Map k a
dict = M.fromList $ fmapToFst f (universe @a)
{-# INLINE inverseMap #-}

{- | Like 'succ', but doesn't fail on 'maxBound'. Instead it returns 'minBound'.
Expand Down
7 changes: 6 additions & 1 deletion src/Relude/Extra/Tuple.hs
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,14 @@ module Relude.Extra.Tuple
, traverseBoth
) where

import Relude
import Relude.Function ((.))
import Relude.Functor (Functor (..), (<$>))
import Relude.Applicative (Applicative (..))


-- $setup
-- >>> import Relude

{- | Creates a tuple by pairing something with itself.
>>> dup "foo"
Expand Down
3 changes: 2 additions & 1 deletion src/Relude/Monoid.hs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ import Relude.Monad.Reexport (Maybe, fromMaybe)
import GHC.Generics (Generic1)

import Relude.Applicative (Alternative, Applicative (..), liftA2)
import Relude.Base (Bounded (..), Enum, Eq, Generic, Ord, Show)
import Relude.Base (Eq, Generic, Ord, Show)
import Relude.Enum (Bounded (..), Enum)
import Relude.Function (($), (.))
import Relude.Functor.Reexport (Functor (..))
import Relude.Monad.Reexport (Monad, MonadFail, MonadPlus)
Expand Down
3 changes: 2 additions & 1 deletion src/Relude/Numeric.hs
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,9 @@ import GHC.Real (Fractional (..), Integral (..), Ratio, Rational, Real (..), Rea
denominator, even, fromIntegral, gcd, lcm, numerator, odd, realToFrac, (^), (^^))
import Numeric.Natural (Natural)

import Relude.Base (Bounded (..), (<), (>))
import Relude.Base ((<), (>))
import Relude.Bool (otherwise)
import Relude.Enum (Bounded (..))
import Relude.Function (($))
import Relude.Monad (Maybe (..))

Expand Down

0 comments on commit 1e5fb60

Please sign in to comment.