packages feed

build-1.1: src/Build/Task/Typed.hs

{-# LANGUAGE CPP, ConstraintKinds, RankNTypes, GADTs #-}
#if __GLASGOW_HASKELL__ < 800
{-# OPTIONS_GHC -Wno-unused-binds #-}
#else
{-# OPTIONS_GHC -Wno-unused-top-binds #-}
#endif
{-# OPTIONS_GHC -Wno-unticked-promoted-constructors #-}

-- | A model of polymorphic tasks, where the value type depends on the key.
-- See the source for an example.
module Build.Task.Typed (Task, dependencies) where

#if __GLASGOW_HASKELL__ < 800
import Control.Applicative
#else
import Data.Functor.Const
#endif

-- | The @fetch@ callback whose result type depends on the type of the key.
type Fetch k f = forall a. k a -> f a

-- | A typed build task.
--
-- A side observation: we could also rewrite the type of `Task` into
--
-- type Task c k = forall f. c f => (forall a. k a -> f a) -> (forall a. k a -> Maybe (f a))
--
-- ...which looks like a morphism between natural transformations. I'll let
-- category theory enthusiasts explain what this strange creature is doing here.
type Task c k = forall f a. c f => Fetch k f -> k a -> Maybe (f a)

-- | A way to show the name of a key.
type ShowKey k = forall a. k a -> String

-- | Extract the names of dependencies.
dependencies :: ShowKey k -> Task Applicative k -> k a -> [String]
dependencies showKey task = maybe [] getConst . task (\k -> Const [showKey k])

----------------------------- GCC versison example -----------------------------
data Version = Version { major :: Int, minor :: Int }
    deriving (Eq, Ord)

data Key a where
    File       :: FilePath -> Key String
    GccVersion :: Key Version

newtype TaskT c k v = TaskT { runT :: forall f. c f => Fetch k f -> f v }

type TasksT c k = forall a. k a -> Maybe (TaskT c k a)

example :: TasksT Monad Key
example (File "release.txt") = Just $ TaskT $ \fetch -> do
    readme  <- fetch (File "README")
    license <- fetch (File "LICENSE")
    return (readme ++ license)
example (File "main.o") = Just $ TaskT $ \fetch -> do
    let source = "main.c"
    version <- fetch GccVersion
    if version >= Version 8 0 then compileNew source
                              else compileOld source
example _ = Nothing

compileNew :: String -> f String
compileNew = undefined

compileOld :: String -> f String
compileOld = undefined

------------------------------------ Example -----------------------------------
data KeyN a where
    Base       :: KeyN Int
    Number     :: KeyN Int
    SplitDigit :: KeyN (Int, Int)
    LastDigit  :: KeyN Int
    BaseDigits :: KeyN [Int]

-- | A build task for some simple typed numeric calculations. We can perform
-- static analysis of this task using the function 'dependencies'. For example:
--
-- @
-- dependencies showKey task Base       == []
-- dependencies showKey task SplitDigit == ["Number","Base"]
-- @
task :: Task Applicative KeyN
task fetch SplitDigit = Just $ divMod <$> fetch Number <*> fetch Base
task fetch LastDigit  = Just $ snd <$> fetch SplitDigit
task fetch BaseDigits = Just $ (\b -> [0..(b - 1)]) <$> fetch Base
task _ _ = Nothing

-- | An example key/value mapping consistent with the build 'task'.
fetch :: Applicative f => Fetch KeyN f
fetch key = pure $ case key of
    Base       -> 10
    Number     -> 2018
    SplitDigit -> (201, 8)
    LastDigit  -> 8
    BaseDigits -> [0..9]

-- | Show the name of a key.
showKey :: ShowKey KeyN
showKey key = case key of
    Base       -> "Base"
    Number     -> "Number"
    SplitDigit -> "SplitDigit"
    LastDigit  -> "LastDigit"
    BaseDigits -> "BaseDigits"