-- -*- coding: utf-8; mode: haskell; -*-

-- File: library/Language/Ninja/Misc/Annotated.hs
--
-- License:
--     Copyright 2017 Awake Security
--
--     Licensed under the Apache License, Version 2.0 (the "License");
--     you may not use this file except in compliance with the License.
--     You may obtain a copy of the License at
--
--       http://www.apache.org/licenses/LICENSE-2.0
--
--     Unless required by applicable law or agreed to in writing, software
--     distributed under the License is distributed on an "AS IS" BASIS,
--     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
--     See the License for the specific language governing permissions and
--     limitations under the License.

{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE RankNTypes     #-}

-- |
--   Module      : Language.Ninja.Misc.Annotated
--   Copyright   : Copyright 2017 Awake Security
--   License     : Apache-2.0
--   Maintainer  : opensource@awakesecurity.com
--   Stability   : experimental
--
--   A typeclass for AST nodes that are annotated with a polymorphic field,
--   which provides a canonical lens into that field.
--
--   @since 0.1.0
module Language.Ninja.Misc.Annotated
  ( Annotated (..), annotation
  ) where

import qualified Control.Lens as Lens

--------------------------------------------------------------------------------

-- | If you have some type that represents an AST node, it is often useful to
--   add a polymorphic "annotation field" to it, which is used for things like
--   source positions.
--
--   Specifically, suppose we have the following AST node type:
--
--   @data Foo = Foo { _fooBar :: !Bar, _fooBaz :: !Baz } deriving (…)@
--
--   Then an annotation field is added by the following process:
--
--   1. Add an extra (final) type parameter @ann@ to the type.
--   2. Add an extra field @_fooAnn :: !ann@.
--   3. Derive instances of 'Functor', 'Foldable', and 'Traversable'.
--   4. If the type is recursive, add a 'Lens.Plated' instance.
--      See "Language.Ninja.AST.Expr" for a complete example of this.
--   5. Write an 'Annotated' instance with the canonical lens given by the
--      @_fooAnn@ field. There are plenty of examples around this library.
--
--   The end result then looks like:
--
--   > data Foo ann
--   >   = Foo
--   >     { _fooAnn :: !ann
--   >     , _fooBar :: !Bar
--   >     , _fooBaz :: !Baz
--   >     }
--   >   deriving (…, Functor, Foldable, Traversable)
--   >
--   > instance Annotated Foo where
--   >   annotation' = …
--
--   @since 0.1.0
class (Functor ty) => Annotated (ty :: * -> *) where
  -- | Given a function that is used when 'fmap'ing any subterms, return a lens
  --   into the "annotation" field.
  --
  --   When writing an instance, keep in mind that @'annotation'' id@ should
  --   just be the typical definition for a lens into the annotation field.
  --
  --   It should also be true that for any @f :: B -> C@ and @g :: A -> B@,
  --
  --   > annotation' (f . g) == annotation' f . annotation' g
  --
  --   @since 0.1.0
  annotation' :: (ann -> ann') -> Lens.Lens (ty ann) (ty ann') ann ann'

-- | This is just shorthand for @'annotation'' id@.
--
--   @since 0.1.0
annotation :: (Annotated ty) => Lens.Lens' (ty ann) ann
annotation = annotation' id

--------------------------------------------------------------------------------