-------------------------------------------------------------------------------
-- |
-- Module      :  ValidLiterals
-- Copyright   :  (C) 2015 Merijn Verstraaten
-- License     :  BSD-style (see the file LICENSE)
-- Maintainer  :  Merijn Verstraaten <merijn@inconsistent.nl>
-- Stability   :  experimental
-- Portability :  portable
--
-- __WARNING: For implementors of new 'Validate' instances only!__
--
-- This module exposes functions whose use is easily screwed up. Users of
-- validated literals should use the interface exported by "ValidLiterals".
-- This module only exists for implementers of new 'Validate' instances.
-------------------------------------------------------------------------------
{-# LANGUAGE DefaultSignatures #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE TemplateHaskell #-}
module ValidLiterals.Class where

import Data.Maybe
import Language.Haskell.TH
import Language.Haskell.TH.Syntax

-- | Class for validated, compile-time, partial conversions from type 'a' to
-- 'b'.
class Validate a b where
    -- | Converts 'a' values into validated 'b' values
    fromLiteral :: a -> Maybe b

    -- | __WARNING: Don't use! For implementors of 'Validate' instances only!__
    --
    -- Produces the actual Typed Template Haskell splice for the validated
    -- value to splice into the source text. Since you may want to implement a
    -- 'Validate' instance for types that cannot have 'Lift' instances, this
    -- function receives __both__ the original 'a' value and the resulting 'b'
    -- as input.
    --
    -- This allows it to either:
    --
    --      1. Splice the resulting 'b' into the source using it's Lift
    --      instance, or a custom splice.
    --      2. Splice the initial 'a' into the source using it's Lift instance,
    --      and perform the conversion again at runtime, and coercing via,
    --      e.g., 'fromJust'. Since 'fromLiteral' is pure (right?!) this should
    --      be perfectly safe.
    --
    -- Clearly, splicing the result directly is much safer (it avoids any
    -- shenanigans with 'unsafePerformIO') and more efficient (no conversion at
    -- runtime). However, this is just not always possible, since, for example,
    -- @ByteString@ does not have a 'Lift' instance and we may still want to
    -- have validated newtypes of @ByteString@.
    --
    -- __Default implementation:__ The default implementation (using
    -- DefaultSignatures) uses 'b''s 'Lift' instance to do the efficient thing
    -- of directly splicing the resulting 'b' into the source.
    spliceValid :: a -> b -> Q (TExp b)
    default spliceValid :: Lift b => a -> b -> Q (TExp b)
    spliceValid _ v = [|| v ||]

-- | Default implementation for 'spliceValid', uses the 'Lift' instance for 'a'
-- and 'fromJust' to redo the conversion at runtime and (unsafely) coerce from
-- 'Maybe b' to 'b'. . Since 'fromLiteral' is pure (right?!) this should be
-- perfectly safe.
hackySpliceValid :: (Lift a, Validate a b) => a -> b -> Q (TExp b)
hackySpliceValid v _ = [|| fromJust (fromLiteral v) ||]