{-# OPTIONS_HADDOCK show-extensions,not-home #-}
-- | Impl is intended to be used as an alternative to the normal default typeclass methods machinery of Haskell.
-- In contrast with @intrinsic-superclasses@, we must specify each link of the implementation heirarchy with an instance of Impl, rather than infer it from the superclass heirarchy.
-- The benefit of this more explicit style is complete control over default methods provided by subclasses, at the cost of some automation for the class creator.
-- Impl is most valuable when instantiating deep (or even undecidably recursive) typeclass hierarchies for multiple new datatypes, which is most common in client code.
module Impl (
  -- * The core Impl class
  Impl(..),Method(..)
  ,Symbol -- | Reexported from @base@
  ,NamedMethods, NamedExpQ
  ,type (:->)
  -- * Utilities for Named arguments
  -- | @impl@ uses "Named" arguments, which work best with @OverloadedLabels@
  ,type (:!) -- | A required named argument.
             --
             -- >>> #foo 'a' :: "foo" :! Char
  ,type (:?) -- | An optional named argument
             --
             -- >>> #foo 'b' :: "foo" :? Char
  ,($$)
  ,defaults -- | A special 'Param' to fill in the remaining 'Optional' arguments with 'Nothing'
            --
            -- @foo :: ("bar" :! String) -> ("baz" :? Char) -> ("quox" :? Int) -> IO ()@
            --
            -- >>> foo $$ #bar "Hello" $$ defaults :: IO ()
  ,arg {- |

'arg' unwraps a named parameter with the specified name. One way to use it is
to match on arguments with @-XViewPatterns@:

@
fn (arg \#t -> t) (arg \#f -> f) = ...
@

This way, the names of parameters can be inferred from the patterns: no type
signature for @fn@ is required. In case a type signature for @fn@ is
provided, the parameters must come in the same order:

@
fn :: "t" :! Integer -> "f" :! Integer -> ...
fn (arg \#t -> t) (arg \#f -> f) = ... -- ok
fn (arg \#f -> f) (arg \#t -> t) = ... -- does not typecheck
@

-}
  , arg'
  ,Param -- | A parameter passable as a named argument. Used implicitly by '($$)' with @OverloadedLabels
  ,NamedF(Arg,Arg') -- | A named argument that could be required or optional depending on the @f@ parameter

  -- * TH reexports and utilities
  ,methodsFor, type (++), TypeQ, DecsQ
  ) where
import Language.Haskell.TH hiding (Name)
import Named
import Named.Internal (Param,NamedF(ArgF))
import GHC.TypeLits (Symbol)
import Impl.Utils


-- | Typeclasses implementing Impl can build declaratios for their entire superclass heirarchy
-- from a collection of required or optional named methods, allowing potentially complex logic for defaulting.
-- See the @example@ internal library for how to implement instances of 'Impl'.
class Impl c where
  type Methods c :: [Method Symbol]
  -- | Instantiate the implementing class along with all its superclasses
  -- Ex: 
  --
  -- > impl @Monad [t|[]|]
  -- >   $$ #return [|\x -> [x]|]
  -- >   $$ #bind   [|flip concatMap|]
  impl :: TypeQ -> NamedMethods c :-> DecsQ

-- | >>> :kind! NamedExpQ '[Required "foo", Optional "bar"]
-- = '["foo" :! ExpQ,"bar" :? ExpQ]
type family NamedExpQ ss where
  NamedExpQ '[] = '[]
  NamedExpQ (Required s ': ss) = (s :! ExpQ) ': NamedExpQ ss
  NamedExpQ (Optional s ': ss) = (s :? ExpQ) ': NamedExpQ ss

-- | "Named" TH 'Exp's for the class method implementations
type NamedMethods c = NamedExpQ (Methods c)

arg' :: Name name -> a -> (name :? a) -> a
{- | A variation of 'arg' for optional arguments. Requires a default value to handle
the case when the optional argument was omitted:

@ fn (arg' \#answer 42 -> ans) = ...  @

In case you want to get a value wrapped in 'Maybe' instead, 'Arg'' -}
arg' = argDef

pattern Arg' :: Maybe a -> name :? a
-- | Construct or match an optional named argument
pattern Arg' a' = ArgF a'

($$) :: WithParam p fn fn' => fn -> Param p -> fn'
{- | Pass a named (optional or required) argument to a function in any order.

     @foo :: ("bar" :! String) -> ("baz" :? Char) -> IO ()@

     >>> foo $$ #baz 'a' :: ("bar" :! String) -> IO ()
-}
($$) = (!)