{-# language MagicHash #-}

-- | Core definitions. This module is internal and provides no guarantees about
-- stability and safety of it's interface.
module FCI.Internal.Definitions (
    Inst
  , Dict
  , inst
  , (==>)
  , Newtype
  ) where

import Data.Coerce (Coercible)
import GHC.Exts    (unsafeCoerce#)

import qualified FCI.Internal.Types as Internal (Inst, Dict)

-------------------------------------------------------------------------------
infixr 0 ==>
infixr 1 :=>

-------------------------------------------------------------------------------
-- | Type that maps constraint to it's representation. You can get hold of
-- representation of some special constraints and classes that use
-- 'FCI.mkInst'.
--
-- For example:
--
-- @
-- class Bar a => Foo a where
--   baz :: a
--   qux :: a -> b -> [(a, b)]
--
-- mkInst 'Foo
-- @
--
-- creates datatype instance:
--
-- @
-- type instance Inst (Foo a) = Dict (Foo a)
-- data instance Dict (Foo a) = Foo{
--     _Bar :: Inst (Bar a)
--   , baz  :: a
--   , qux  :: forall b. a -> b -> [(a, b)]
--   }
-- @
--
-- You can get hold of representation of global instance using 'FCI.inst'. You
-- are free to modify and read it and you can use ('FCI.==>') to apply it as
-- constraint in context of some subexpression. See 'FCI.mkInst' for more info
-- about format of generated representation.
type Inst c = Internal.Inst c

-------------------------------------------------------------------------------
-- | Type of representation of class instance. You can get instance for your
-- class using 'FCI.mkInst' and access value of global instance using
-- 'FCI.inst'. Prefer 'FCI.Inst' in signatures when working with constraint
-- representations.
type Dict = Internal.Dict

-------------------------------------------------------------------------------
-- | /Reflects/ constraint into correspoding representation - can be used to
-- access normal class instances from the environment. This function is meant
-- to be used with @TypeApplications@ when it's usage is ambiguous.
--
-- TODO: example
inst :: forall c. c => Inst c
inst = case unsafeCoerce# id :: c :=> Inst c of Wants d -> d

-------------------------------------------------------------------------------
-- | /Reifies/ first class instance into constraint in context of supplied
-- continuation.
--
-- For example:
--
-- >>> newtype Foo a = Foo a deriving Show
-- >>> coerceFunctor @Foo ==> (+1) <$> Foo 1
-- Foo 2
(==>) :: forall c r. Inst c -> (c => r) -> r
d ==> x = unsafeCoerce# (Wants @c @r x) d

-------------------------------------------------------------------------------
-- | Wrapper for value @r@ requiring constraint @c@. Used by 'inst' and ('==>')
-- to satisfy typechecker.
newtype c :=> r where
  Wants :: (c => r) -> c :=> r

-------------------------------------------------------------------------------
-- | Allows to 'Data.Coerce.coerce' type back and forth between it's argument
-- when safe.
type Newtype f = forall a. (Coercible a (f a), Coercible (f a) a)