packages feed

hnix-0.15.0: src/Nix/Utils.hs

{-# language NoImplicitPrelude #-}
{-# 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
  ( KeyMap
  , TransformF
  , Transform
  , Alg

  , Path(..)
  , isAbsolute
  , (</>)
  , joinPath
  , splitDirectories
  , takeDirectory
  , takeFileName
  , takeBaseName
  , takeExtension
  , takeExtensions
  , addExtension
  , dropExtensions
  , replaceExtension

  , Has(..)
  , trace
  , traceM
  , stub
  , pass
  , whenTrue
  , whenFalse
  , list
  , whenText
  , free
  , whenJust
  , dup
  , mapPair
  , both
  , readFile
  , traverseM
  , lifted
  , loebM
  , adi
  , 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.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 qualified Data.Text.IO                 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

-- | To have 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

-- This set of @Path@ funcs is to control system filepath types & typesafety and to easy migrate from FilePath to anything suitable (like @path@ or so).

-- | @isAbsolute@ specialized to @Path@.
isAbsolute :: Path -> Bool
isAbsolute = coerce FilePath.isAbsolute

-- | @(</>)@ specialized to @Path@
(</>) :: Path -> Path -> Path
(</>) = coerce (FilePath.</>)
infixr 5 </>

-- | @joinPath@ specialized to @Path@
joinPath :: [Path] -> Path
joinPath = coerce FilePath.joinPath

-- | @splitDirectories@ specialized to @Path@
splitDirectories :: Path -> [Path]
splitDirectories = coerce FilePath.splitDirectories

-- | @takeDirectory@ specialized to @Path@
takeDirectory :: Path -> Path
takeDirectory = coerce FilePath.takeDirectory

-- | @takeFileName@ specialized to @Path@
takeFileName :: Path -> Path
takeFileName = coerce FilePath.takeFileName

-- | @takeBaseName@ specialized to @Path@
takeBaseName :: Path -> String
takeBaseName = coerce FilePath.takeBaseName

-- | @takeExtension@ specialized to @Path@
takeExtension :: Path -> String
takeExtension = coerce FilePath.takeExtensions

-- | @takeExtensions@ specialized to @Path@
takeExtensions :: Path -> String
takeExtensions = coerce FilePath.takeExtensions

addExtension :: Path -> String -> Path
addExtension = coerce FilePath.addExtension

-- | @dropExtensions@ specialized to @Path@
dropExtensions :: Path -> Path
dropExtensions = coerce FilePath.dropExtensions

-- | @replaceExtension@ specialized to @Path@
replaceExtension :: Path -> String -> Path
replaceExtension = coerce FilePath.replaceExtension


-- | > Hashmap Text -- type synonym
type KeyMap = HashMap Text

-- | 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.
-- ...
-- You got me, it is a natural transformation.
type TransformF f a = (f -> a) -> f -> a

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

loebM :: (MonadFix m, Traversable t) => t (t a -> m a) -> m (t a)
-- Sectioning here insures optimization happening.
loebM f = mfix $ \a -> (`traverse` f) ($ a)
{-# inline loebM #-}

--  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 =
  do
    lftd <- liftWith (\run -> f (run . k))
    restoreT $ pure lftd

-- | 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


-- | Analog for @bool@ or @maybe@, for list-like cons structures.
list
  :: Foldable t
  => b -> (t a -> b) -> t a -> b
list e f l =
  bool
    (f l)
    e
    (null l)
{-# inline list #-}

whenText
  :: a -> (Text -> a) -> Text -> a
whenText e f t =
  bool
    (f t)
    e
    (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 #-}


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 #-}


-- | 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 #-}


-- | Duplicates object into a tuple.
dup :: a -> (a, a)
dup x = (x, x)
{-# inline dup #-}

-- | From @utility-ht@ for tuple laziness.
mapPair :: (a -> c, b -> d) -> (a,b) -> (c,d)
mapPair ~(f,g) ~(a,b) = (f a, g b)
{-# inline mapPair #-}

-- 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 #-}

readFile :: Path -> IO Text
readFile = Text.readFile . coerce

traverseM
  :: ( Applicative m
     , Applicative f
     , Traversable t
     )
  => ( a
     -> m (f b)
     )
  -> t a
  -> m (f (t b))
traverseM f x = sequenceA <$> traverse f x