{-# LANGUAGE Safe #-}

{- |
Module                  : Relude.Extra.Newtype
Copyright               : (c) 2018-2023 Kowainik
SPDX-License-Identifier : MIT
Maintainer              : Kowainik <xrom.xkov@gmail.com>
Stability               : Stable
Portability             : Portable

Functions to ease work with @newtypes@.

@since 0.2.0
-}

module Relude.Extra.Newtype
    ( un
    , wrap
    , under
    , under2
    , underF2
    , (#.)
    ) where

import Relude


-- $setup
-- >>> :set -XTypeApplications
-- >>> import Data.Semigroup (Max (..))

{- | Unwraps value from @newtype@.

>>> newtype Size = Size Int deriving Show
>>> un @Int (Size 5)
5
>>> un (Size 5) == length ['a', 'x', 'b']
False

@since 0.2.0
-}
un :: forall a n . Coercible a n => n -> a
un :: forall a n. Coercible a n => n -> a
un = coerce :: forall a b. Coercible a b => a -> b
coerce
{-# INLINE un #-}

{- | Wraps value to @newtype@. Behaves exactly as 'un' but has more meaningful
name in case you need to convert some value to @newtype@.

>>> newtype Flag = Flag Bool deriving (Show, Eq)
>>> wrap False == Flag True
False

@since 0.2.0
-}
wrap :: forall n a . Coercible a n => a -> n
wrap :: forall n a. Coercible a n => a -> n
wrap = coerce :: forall a b. Coercible a b => a -> b
coerce
{-# INLINE wrap #-}

{- | Applies function to the content of @newtype@. This function is not supposed
to be used on @newtype@s that are created with the help of smart constructors.

>>> newtype Foo = Foo Bool deriving Show
>>> under not (Foo True)
Foo False
>>> newtype Bar = Bar String deriving Show
>>> under (filter (== 'a')) (Bar "abacaba")
Bar "aaaa"

@since 0.2.0
-}
under :: forall n a . Coercible a n => (n -> n) -> (a -> a)
under :: forall n a. Coercible a n => (n -> n) -> a -> a
under = coerce :: forall a b. Coercible a b => a -> b
coerce
{-# INLINE under #-}

{- | Lift binary function for @newtype@s to work over underlying @newtype@
representation.

>>> under2 @(Sum Int) (<>) (3 :: Int) 4
7
>>> under2 @All (<>) True False
False

@since 0.3.0
-}
under2 :: forall n a . Coercible a n => (n -> n -> n) -> (a -> a -> a)
under2 :: forall n a. Coercible a n => (n -> n -> n) -> a -> a -> a
under2 = coerce :: forall a b. Coercible a b => a -> b
coerce
{-# INLINE under2 #-}

{- | Version of 'under2' that works on @newtype@s parametrized by their
representation. Provided for convenience.

>>> underF2 @Sum (<>) (3 :: Int) 4
7
>>> underF2 @Max (<>) 'p' 't'
't'

@since 0.3.0
-}
underF2 :: forall n a . Coercible a (n a) => (n a -> n a -> n a) -> (a -> a -> a)
underF2 :: forall (n :: * -> *) a.
Coercible a (n a) =>
(n a -> n a -> n a) -> a -> a -> a
underF2 = coerce :: forall a b. Coercible a b => a -> b
coerce
{-# INLINE underF2 #-}

{- | Coercible composition. This function allows to write more efficient
implementations of function compositions over @newtypes@.

@since 0.3.0
-}
(#.) :: Coercible b c => (b -> c) -> (a -> b) -> (a -> c)
#. :: forall b c a. Coercible b c => (b -> c) -> (a -> b) -> a -> c
(#.) b -> c
_f = coerce :: forall a b. Coercible a b => a -> b
coerce
{-# INLINE (#.) #-}