language-ninja-0.1.0: library/Language/Ninja/Misc/Annotated.hs
-- -*- 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
--------------------------------------------------------------------------------