{-# LANGUAGE TypeSynonymInstances #-}
{-# OPTIONS_GHC -Wall #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}
----------------------------------------------------------------------
-- |
-- Module      :  Data.Partial
-- Copyright   :  (c) Conal Elliott 2007
-- License     :  BSD3
-- 
-- Maintainer  :  conal@conal.net
-- Stability   :  experimental
-- Portability :  portable
-- 
-- A monoid 'Partial' of partial values.  See the [Teaser] and [Solution] blog
-- posts.
-- 
--   [Teaser]:   <http://conal.net/blog/posts/a-type-for-partial-values>
--   [Solution]: <http://conal.net/blog/posts/implementing-a-type-for-partial-values>
-- 
-- Also defines a 'FunAble' instance, so that @FunA Partial@ is an arrow.
----------------------------------------------------------------------

module Data.Partial
  (
  -- * Partial values
    Partial, PartialX, valp, pval
  , pUnElt, pUnArg, pUnRes, pUnSrc
  -- * Support for arrow partial value arrow
  -- via 'FunAble' instance
  ) where

import Prelude hiding (zip,unzip)
import Data.Monoid
import Control.Arrow

import Control.Compose (FunAble(..),inEndo)

import Data.Zip

-- | Partial value.  Represented an endomorphism, which is a 'Monoid'
-- under 'id' and '(.)'.  Then 'mempty' is the completely undefined value,
-- and in @u `@'mappend'@` v@, @v@ selectively replaces parts of @u@.  The
-- 'Endo' instances for 'Pair', 'Unpair', 'Copair', 'Unfun', and 'Cofun'
-- are all very useful on partial values.
type Partial = Endo

type PartialX a b = Partial a -> Partial b

-- | Treat a full value as a partial one.  Fully overrides any
-- \"previous\" (earlier argument to @mappend@) partial value.
valp :: c -> Partial c
valp c = Endo (const c)

-- | Force a partial value into a full one, filling in bottom for any
-- missing parts.
pval :: Partial c -> c
pval (Endo f) = f (error "Partial: absent info")


-- | Inverse to \"element\" access, on all elements.  A way to inject some
-- info about every element.  For @f@, consider '[]', @(->) a@,
-- @Event@, etc.

pUnElt :: Functor f => PartialX a (f a)
pUnElt = inEndo fmap

-- | Provide in info about a function argument
pUnArg :: PartialX u (u -> v)
pUnArg = inEndo (flip (.))            -- \ uv -> \ u -> uv (uu u)

-- | Provide info about a function result
pUnRes :: PartialX v (u -> v)
pUnRes = inEndo (.)

-- | Inject a partial argument-source into a partial function-sink.
pUnSrc :: PartialX a ((a -> b) -> o)
pUnSrc = pUnArg . pUnArg


{----------------------------------------------------------
    'FunA' support for arrows on partial values
----------------------------------------------------------}

instance FunAble Partial where
  arrFun    = pArr
  firstFun  = pFirst
  secondFun = pSecond

-- I don't think we can define @arr f@ unless we can invert @f@.  Same
-- problem exists in "There and back again: arrows for invertible
-- programming".  Suggests refactoring Arrow.

pArr :: (a->b) -> PartialX a b
pArr f = inEndo $ (f .) . (. inv f)
 where
   inv :: (a->b) -> (b->a)
   inv = error "inv -- can't do it."

-- Since @pArr == fmap@, nor can we make a @Functor@ instance of
-- @PartialFun a@.

pFirst  :: PartialX a a' -> PartialX (a,b) (a',b)
pFirst  f = uncurry zip . first f . unzip

pSecond :: PartialX b b' -> PartialX (a,b) (a,b')
pSecond g = uncurry zip . second g . unzip

-- The following is not quite equivalent, since mappend doesn't commute.
-- 
-- pSecond g ab = pUnSnd (g b) `mappend` pUnFst a
--   where (a,b) = dsPZip ab


-- TODO: DeepArrow instance for PartialFun (perhaps in the DeepArrow
-- library) .  Some methods are easy, and some hard or impossible.