{-# LANGUAGE Rank2Types #-}
-- | Support for OO-like prototypes.
module Data.Prototype where

import Data.Function (fix)

-- | A prototype. Typically the parameter will be a record type.
-- Fields can be defined in terms of others fields, with the
-- idea that some of these definitons can be overridden.
--
-- Example:
--
-- > data O = O {f1, f2, f3 :: Int}
-- >     deriving Show
-- > o1 = Proto $ \self -> O
-- >   {
-- >    f1 = 1,
-- >    f2 = f1 self + 1,  -- 'f1 self' refers to the overriden definition of f1.
-- >    f3 = f1 self + 2
-- >   }
newtype Proto a = Proto {fromProto :: a -> a}

-- | Get the value of a prototype.
-- This can return bottom in case some fields are recursively defined in terms of each other.
extractValue :: Proto t -> t
extractValue (Proto o) = fix o

-- | Override a prototype. Fields can be defined in terms of their definition in the base prototype.
--
-- Example:
--
-- > o2 = o1 `override` \super self -> super 
-- >    {
-- >    f1 = f1 super + 10,
-- >    f3 = f3 super + 1
-- >    }

override :: Proto a -> (a -> a -> a) -> Proto a
override (Proto base) extension = Proto (\self -> let super = base self 
                                                  in extension super self)

-- | Field access
(.->) :: forall t a. Proto t -> (t -> a) -> a
p .-> f = f (extractValue p)