module Data.Codec.Field
  ( 
  -- * First-class record construction
    Field(..)
  , Build(..)
  , Con(..)
  , ($>>), (>>>), done
  , X(X), Buildable(..)
  , having, build
  ) where

import Control.Applicative
import Control.Category
import Prelude hiding ((.), id)

-- | `Field`s partially apply constructors and replace arguments with this type.
data X = X

-- | The class of constructor applications that have been completely filled in by composing
-- `Build`s. If you see an error message involving this, it means that you forgot to specify
-- a `Build` for a field.
class Buildable r a where
  give :: a -> r

instance Buildable r r where
  give = id

instance Buildable r b => Buildable r (X -> b) where
  give f = give $ f X

-- | Describes how to apply a constructor argument and how to extract from a record.
-- @y@ should be @x@ with one argument knocked out: e. g.
--
-- @
-- Field MyType Int (Int -> a2 -> MyType) (X -> a2 -> MyType)
-- @
data Field r a x y = Field (a -> x -> y) (r -> a)

-- Static (Backwards f) + phantom parameter
-- | An ongoing record construction of an @r@ in context @f@.
-- Applicative actions are sequenced in the direction of `>>>`.
newtype Build r f x y = Build (f (x -> y))

-- | Combine a `Field` and a way to produce an @a@ to get a `Build`.
having :: Functor f => Field r a x y -> f a -> Build r f x y
having (Field c _) p = Build (c <$> p)

-- | No-op `Build` (same as `id`).
done :: Applicative f => Build r f x x
done = id

instance Applicative f => Category (Build r f) where
  id = Build (pure id)
  Build f . Build g
    = Build ((>>>) <$> g <*> f)

-- | Finish a construction given a constructor.
build :: (Functor f, Buildable r y) => x -> Build r f x y -> f r
build x (Build b)
  = (\f -> give $ f x) <$> b

-- | Infix version of `build`.
($>>) :: (Functor f, Buildable r y) => x -> Build r f x y -> f r
($>>) = build
infixr 1 $>>

-- | A constructor for a given record and a way to check whether it has it.
data Con r x = Con x (r -> Bool)