packages feed

registry-0.4.0.0: src/Data/Registry/Internal/Make.hs

{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-}

-- |
--  Untyped implementation of the functionalities in
--    'Data.Registry.Make'
module Data.Registry.Internal.Make where

import Data.List qualified as L hiding (unlines)
import Data.Registry.Internal.Dynamic
import Data.Registry.Internal.Reflection (showSingleType)
import Data.Registry.Internal.Registry
import Data.Registry.Internal.Stack
import Data.Registry.Internal.Types
import Data.Text qualified as T
import Protolude as P hiding (Constructor)
import Type.Reflection

-- * WARNING: HIGHLY UNTYPED IMPLEMENTATION !

-- | Make a value from a desired output type represented by SomeTypeRep
--   and a list of possible constructors
--   A 'Context' is passed in the form of a stack of the types we are trying to build so far
--  Functions is the list of all the constructors in the Registry
--  Specializations is a list of specific values to use in a given context, overriding the normal search
--  Modifiers is a list of functions to apply right before a value is stored in the Registry
makeUntyped :: SomeTypeRep -> Context -> Functions -> Specializations -> Modifiers -> Stack (Maybe Value)
makeUntyped targetType context functions specializations modifiers = do
  values <- getValues
  -- is there already a value with the desired type? Or a specialization
  let foundValue = findValueOrSpecialization targetType context specializations values

  case foundValue of
    Nothing ->
      makeWithConstructor
    -- existing value
    Just (Right v) -> do
      modified <- storeValue modifiers v
      pure (Just modified)
    -- specialization
    Just (Left specialization) -> do
      -- if the specialization is just a value, return it
      case createValueFromSpecialization context specialization of
        UntypedValue v -> do
          modified <- storeValue modifiers v
          pure (Just modified)
        UntypedFunction f -> do
          -- we don't fail the building if a specialization cannot be applied
          -- we try to use an already created value or build one from scratch
          catchError (makeWithFunction f $ Just specialization) $ \_ ->
            case findCompatibleCreatedValue targetType specializations values of
              Just v -> pure (Just v)
              Nothing -> makeWithConstructor
  where
    makeWithConstructor :: Stack (Maybe Value)
    makeWithConstructor = do
      -- if not, is there a way to build such value?
      case findFunction targetType functions of
        Nothing ->
          lift $
            Left $
              "When trying to create the following values\n\n          "
                <> T.intercalate "\nrequiring " (showContextTargets context)
                <> "\n\nNo constructor was found for "
                <> showSingleType targetType
        Just f ->
          makeWithFunction f Nothing

    makeWithFunction :: Function -> Maybe Specialization -> Stack (Maybe Value)
    makeWithFunction f mSpecialization = do
      let inputTypes = collectInputTypes f
      inputs <- makeInputs f inputTypes context functions specializations modifiers

      if length inputs /= length inputTypes
        then do
          -- report an error if we cannot make enough input parameters to apply the function

          let madeInputTypes = fmap valueDynTypeRep inputs
          let missingInputTypes = inputTypes L.\\ madeInputTypes
          lift . Left . T.unlines $
            ["could not make all the inputs for ", show (funDescription f), ". Only "]
              <> (show <$> inputs)
              <> ["could be made. Missing"]
              <> fmap show missingInputTypes
        else do
          -- else apply the function and store the output value in the registry
          value <- lift $ applyFunction f inputs
          let valueWithContext =
                case (mSpecialization, value) of
                  (Just s, CreatedValue d desc Nothing deps) ->
                    CreatedValue d desc (Just (SpecializationContext context s)) deps
                  _ ->
                    value
          modified <- storeValue modifiers valueWithContext

          functionApplied modified inputs
          pure (Just modified)

-- | Show the target type and possibly the constructor function requiring it
--   for every target type in the context
showContextTargets :: Context -> [Text]
showContextTargets (Context context) =
  fmap
    ( \(t, f) ->
        case f of
          Nothing -> show t
          Just function -> show t <> "\t\t\t(required for the constructor " <> show function <> ")"
    )
    (reverse context)

-- | Make the input values of a given function
--   When a value has been made it is placed on top of the
--   existing registry so that it is memoized if needed in
--   subsequent calls
makeInputs ::
  Function ->
  -- | input types to build
  [SomeTypeRep] ->
  -- | current context of types being built
  Context ->
  -- | available functions to build values
  Functions ->
  -- | list of values to use when in a specific context
  Specializations ->
  -- | modifiers to apply before storing made values
  Modifiers ->
  Stack [Value] -- list of made values
makeInputs _ [] _ _ _ _ = pure []
makeInputs function (i : ins) c@(Context context) functions specializations modifiers =
  if i `elem` contextTypes c
    then
      lift $
        Left $
          toS $
            T.unlines $
              ["cycle detected! The current types being built are "]
                <> (show <$> context)
                <> ["But we are trying to build again " <> show i]
    else do
      madeInput <- makeUntyped i (Context ((i, Just (funDynTypeRep function)) : context)) functions specializations modifiers
      case madeInput of
        Nothing ->
          -- if one input cannot be made, iterate with the rest for better reporting
          -- of what could be eventually made
          makeInputs function ins (Context context) functions specializations modifiers
        Just v ->
          (v :) <$> makeInputs function ins (Context context) functions specializations modifiers