hnix-0.17.0: src/Nix/Utils.hs
{-# language CPP #-}
{-# language GeneralizedNewtypeDeriving #-}
-- | This is a module of custom "Prelude" code.
-- It is for import for projects other then @HNix@.
-- For @HNix@ - this module gets reexported by "Prelude", so for @HNix@ please fix-up pass-through there.
module Nix.Utils
( stub
, pass
, dup
, both
, mapPair
, iterateN
, nestM
, applyAll
, traverse2
, lifted
, whenTrue
, whenFalse
, whenJust
, isPresent
, handlePresence
, whenText
, free
, Path(..)
, isAbsolute
, (</>)
, joinPath
, splitDirectories
, takeDirectory
, takeFileName
, takeBaseName
, takeExtension
, takeExtensions
, addExtension
, dropExtensions
, replaceExtension
, readFile
, Alg
, Transform
, TransformF
, loebM
, adi
, Has(..)
, askLocal
, KeyMap
, trace
, traceM
, module X
)
where
import Relude hiding ( pass
, force
, readFile
, whenJust
, whenNothing
, trace
, traceM
)
import Data.Binary ( Binary )
import Data.Data ( Data )
import Codec.Serialise ( Serialise )
import Control.Monad ( foldM )
import Control.Monad.Fix ( MonadFix(..) )
import Control.Monad.Free ( Free(..) )
import Control.Monad.Trans.Control ( MonadTransControl(..) )
import qualified Data.Aeson as A
import Data.Fix ( Fix(..) )
import qualified Data.Text as Text
import Lens.Family2 as X
( view
, over
, LensLike'
, Lens'
)
import Lens.Family2.Stock ( _1
, _2
)
import qualified System.FilePath as FilePath
#if ENABLE_TRACING
import qualified Relude.Debug as X
#else
-- Well, since it is currently CPP intermingled with Debug.Trace, required to use String here.
trace :: String -> a -> a
trace = const id
{-# inline trace #-}
traceM :: Monad m => String -> m ()
traceM = const stub
{-# inline traceM #-}
#endif
-- * Helpers
-- After migration from the @relude@ - @relude: pass -> stub@
-- | @pure mempty@: Short-curcuit, stub.
stub :: (Applicative f, Monoid a) => f a
stub = pure mempty
{-# inline stub #-}
-- | Alias for 'stub', since "Relude" has more specialized @pure ()@.
pass :: (Applicative f) => f ()
pass = stub
{-# inline pass #-}
-- | Duplicates object into a tuple.
dup :: a -> (a, a)
dup x = (x, x)
{-# inline dup #-}
-- | Apply a single function to both components of a pair.
--
-- > both succ (1,2) == (2,3)
--
-- Taken From package @extra@
both :: (a -> b) -> (a, a) -> (b, b)
both f (x,y) = (f x, f y)
{-# inline both #-}
-- | Gives tuple laziness.
--
-- Takem from @utility-ht@.
mapPair :: (a -> c, b -> d) -> (a,b) -> (c,d)
mapPair ~(f,g) ~(a,b) = (f a, g b)
{-# inline mapPair #-}
iterateN
:: forall a
. Int -- ^ Recursively apply 'Int' times
-> (a -> a) -- ^ the function
-> a -- ^ starting from argument
-> a
iterateN n f x =
-- It is hard to read - yes. It is a non-recursive momoized action - yes.
fix ((<*> (0 /=)) . ((bool x . f) .) . (. pred)) n
nestM
:: Monad m
=> Int -- ^ Recursively apply 'Int' times
-> (a -> m a) -- ^ function (Kleisli arrow).
-> a -- ^ to value
-> m a -- ^ & join layers of 'm'
nestM 0 _ x = pure x
nestM n f x =
foldM (const . f) x $ replicate @() n mempty -- fuses. But also, can it be fix join?
{-# inline nestM #-}
-- | In `foldr` order apply functions.
applyAll :: Foldable t => t (a -> a) -> a -> a
applyAll = flip (foldr id)
traverse2
:: ( Applicative m
, Applicative n
, Traversable t
)
=> ( a
-> m (n b)
) -- ^ Run function that runs 2 'Applicative' actions
-> t a -- ^ on every element in 'Traversable'
-> m (n (t b)) -- ^ collect the results.
traverse2 f x = sequenceA <$> traverse f x
-- 2021-08-21: NOTE: Someone needs to put in normal words, what this does.
-- This function is pretty spefic & used only once, in "Nix.Normal".
lifted
:: (MonadTransControl u, Monad (u m), Monad m)
=> ((a -> m (StT u b)) -> m (StT u b))
-> (a -> u m b)
-> u m b
lifted f k =
restoreT . pure =<< liftWith (\run -> f (run . k))
-- * Eliminators
whenTrue :: (Monoid a)
=> a -> Bool -> a
whenTrue =
bool
mempty
{-# inline whenTrue #-}
whenFalse :: (Monoid a)
=> a -> Bool -> a
whenFalse f =
bool
f
mempty
{-# inline whenFalse #-}
whenJust
:: Monoid b
=> (a -> b)
-> Maybe a
-> b
whenJust =
maybe
mempty
{-# inline whenJust #-}
isPresent :: Foldable t => t a -> Bool
isPresent = not . null
{-# inline isPresent #-}
-- | 'maybe'-like eliminator, for foldable empty/inhabited structures.
handlePresence :: Foldable t => b -> (t a -> b) -> t a -> b
handlePresence d f t =
bool
d
(f t)
(isPresent t)
{-# inline handlePresence #-}
whenText
:: a -> (Text -> a) -> Text -> a
whenText e f t =
bool
e
(f t)
(not $ Text.null t)
-- | Lambda analog of @maybe@ or @either@ for Free monad.
free :: (a -> b) -> (f (Free f a) -> b) -> Free f a -> b
free fP fF fr =
case fr of
Pure a -> fP a
Free fa -> fF fa
{-# inline free #-}
-- * Path
-- | Explicit type boundary between FilePath & String.
newtype Path = Path FilePath
deriving
( Eq, Ord, Generic
, Typeable, Data, NFData, Serialise, Binary, A.ToJSON, A.FromJSON
, Show, Read, Hashable
, Semigroup, Monoid
)
instance ToText Path where
toText = toText @String . coerce
instance IsString Path where
fromString = coerce
-- ** Path functions
-- | This set of @Path@ funcs is to control system filepath types & typesafety and to easily migrate from FilePath to anything suitable (like @path@ or so).
-- | 'Path's 'FilePath.isAbsolute'.
isAbsolute :: Path -> Bool
isAbsolute = coerce FilePath.isAbsolute
-- | 'Path's 'FilePath.(</>)'.
(</>) :: Path -> Path -> Path
(</>) = coerce (FilePath.</>)
infixr 5 </>
-- | 'Path's 'FilePath.joinPath'.
joinPath :: [Path] -> Path
joinPath = coerce FilePath.joinPath
-- | 'Path's 'FilePath.splitDirectories'.
splitDirectories :: Path -> [Path]
splitDirectories = coerce FilePath.splitDirectories
-- | 'Path's 'FilePath.takeDirectory'.
takeDirectory :: Path -> Path
takeDirectory = coerce FilePath.takeDirectory
-- | 'Path's 'FilePath.takeFileName'.
takeFileName :: Path -> Path
takeFileName = coerce FilePath.takeFileName
-- | 'Path's 'FilePath.takeBaseName'.
takeBaseName :: Path -> String
takeBaseName = coerce FilePath.takeBaseName
-- | 'Path's 'FilePath.takeExtension'.
takeExtension :: Path -> String
takeExtension = coerce FilePath.takeExtensions
-- | 'Path's 'FilePath.takeExtensions'.
takeExtensions :: Path -> String
takeExtensions = coerce FilePath.takeExtensions
-- | 'Path's 'FilePath.addExtensions'.
addExtension :: Path -> String -> Path
addExtension = coerce FilePath.addExtension
-- | 'Path's 'FilePath.dropExtensions'.
dropExtensions :: Path -> Path
dropExtensions = coerce FilePath.dropExtensions
-- | 'Path's 'FilePath.replaceExtension'.
replaceExtension :: Path -> String -> Path
replaceExtension = coerce FilePath.replaceExtension
-- | 'Path's 'FilePath.readFile'.
readFile :: MonadIO m => Path -> m Text
readFile = fmap decodeUtf8 . readFileBS . coerce
-- * Recursion scheme
-- | F-algebra defines how to reduce the fixed-point of a functor to a value.
-- > type Alg f a = f a -> a
type Alg f a = f a -> a
-- | Do according transformation.
--
-- It is a transformation of a recursion scheme.
-- See @TransformF@.
type Transform f a = TransformF (Fix f) a
-- | Do according transformation.
--
-- It is a transformation between functors.
type TransformF f a = (f -> a) -> f -> a
loebM :: (MonadFix m, Traversable t) => t (t a -> m a) -> m (t a)
loebM f = mfix $ \a -> (`traverse` f) ($ a)
{-# inline loebM #-}
-- | adi is Abstracting Definitional Interpreters:
--
-- https://arxiv.org/abs/1707.04755
--
-- All ADI does is interleaves every layer of evaluation by inserting intermitten layers between them, in that way the evaluation can be extended/embelished in any way wanted. Look at its use to see great examples.
--
-- Essentially, it does for evaluation what recursion schemes do for
-- representation: allows threading layers through existing structure, only
-- in this case through behavior.
adi
:: Functor f
=> Transform f a
-> Alg f a
-> Fix f
-> a
adi g f = g $ f . (adi g f <$>) . unFix
-- * Has lens
class Has a b where
hasLens :: Lens' a b
instance Has a a where
hasLens f = f
instance Has (a, b) a where
hasLens = _1
instance Has (a, b) b where
hasLens = _2
-- | Retrive monad state by 'Lens''.
askLocal :: (MonadReader t m, Has t a) => m a
askLocal = asks $ view hasLens
-- * Other
-- | > Hashmap Text -- type synonym
type KeyMap = HashMap Text