HList-0.3.0: examples/TIPTransformM.hs
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE MultiParamTypeClasses, FlexibleInstances #-}
{-# LANGUAGE ScopedTypeVariables, UndecidableInstances #-}
{-# LANGUAGE TypeFamilies #-} -- !TF
-- Transforming a TIP: applying to a TIP a (polyvariadic) function
-- that takes arguments from a TIP and updates the TIP with the result.
-- The monadic version.
-- This file contains two versions of the code.
-- The comments -- !Simple and -- !TF distinguish the versions
--
-- In more detail: we have a typed-indexed collection TIP and we
-- would like to apply a transformation function to it, whose argument
-- types and the result type are all in the TIP. The function should locate
-- its arguments based on their types, and update the TIP
-- with the result. The function may have any number of arguments,
-- including zero; the order of arguments should not matter.
-- The problem was posed by Andrew U. Frank on Haskell-Cafe, Sep 10, 2009.
-- http://www.haskell.org/pipermail/haskell-cafe/2009-September/066217.html
-- The problem is an interesting variation of the keyword argument problem.
-- In March 2010, Andrew Frank extended the problem for monadic operations.
-- This is the monadic version of TIPTransform.hs in the present directory.
module TIPTransformM where
import Data.HList
import Control.Monad.Identity
-- We start with the examples
newtype MyVal = MyVal Int deriving Show
-- A specialized version of return for the Identity monad.
-- It is needed only for the Simple version of the code,
-- to tell the type checker the monad in which the computation is
-- taking place.
-- For the TF version of the code, we can use the ordinary return
-- in place of retI.
retI :: a -> Identity a
retI = return
-- A sample TIP
tip1 = MyVal 20 .*. (1::Int) .*. True .*. (3.5::Float) .*. emptyTIP
-- TIP (HCons (MyVal 20) (HCons 1 (HCons True (HCons 3.5 HNil))))
-- Update the Int component of tip1 to 2. The Int component must
-- exist. Otherwise, it is a type error
-- tip2 = runIdentity $ ttipM (retI (2::Int)) tip1 -- !Simple
tip2 = runIdentity $ ttipM (return (2::Int)) tip1 -- !TF
-- TIP (HCons (MyVal 20) (HCons 2 (HCons True (HCons 3.5 HNil))))
-- Negate the boolean component of tip1
-- tip3 = runIdentity $ ttipM (retI . not) tip1 -- !Simple
tip3 = runIdentity $ ttipM (return . not) tip1 -- !TF
-- TIP (HCons (MyVal 20) (HCons 1 (HCons False (HCons 3.5 HNil))))
-- Update the Int component from the values of two other components
tip4 = runIdentity $ ttipM (\(MyVal x) y -> retI $ x+y) tip1
-- TIP (HCons (MyVal 20) (HCons 21 (HCons True (HCons 3.5 HNil))))
-- Update the MyVal component from the values of three other components
tip5 = runIdentity $
ttipM (\b (MyVal x) y -> retI $ MyVal $ if b then x+y else 0) tip1
-- TIP (HCons (MyVal 21) (HCons 1 (HCons True (HCons 3.5 HNil))))
-- The same but with the permuted argument order.
-- The order of arguments is immaterial: the values will be looked up using
-- their types
tip5' = runIdentity $
ttipM (\b y (MyVal x)-> retI $ MyVal $ if b then x+y else 0) tip1
-- TIP (HCons (MyVal 21) (HCons 1 (HCons True (HCons 3.5 HNil))))
-- Andrew Frank's test
-- tip6 :: IO (TIP (HCons MyVal (HCons Int (HCons Bool (HCons Float HNil)))))
tip6 :: IO (TIP (MyVal ': Int ': Bool ': Float ': '[]))
tip6 = ttipM op6 tip1
op6 :: MyVal -> Bool -> IO MyVal
op6 (MyVal x) b = do
let m = if b then MyVal (x `div` 4) else MyVal (x * 4)
putStrLn $ "MyVal is now " ++ show m
-- ==>> MyVal 5
return m
-- TIP (HCons (MyVal 5) (HCons 1 (HCons True (HCons 3.5 HNil))))
{- -- !Simple
-- The Simple implementation
-- The drawback is the need to let the type checker know the monad in which the
-- computations take place. That is why we had to use retI in the above
-- code, which is a specialized version of return for the Identity monad.
-- In op6, the presence of putStrLn unambiguously specified the monad, viz. IO,
-- so no special return are required.
class Monad m => TransTIPM m op db where
ttipM :: op -> db -> m db
-- If the operation is the computation in the desired monad,
-- the type of the computation must match an element of TIP.
instance (Monad m,
HTypeIndexed db, HUpdateAtHNat n op db db, HType2HNat op db n)
=> TransTIPM m (m op) (TIP db) where
ttipM op db = do
op' <- op
return $ tipyUpdate op' db
-- If op is not a computation in the desired monad m,
-- it must be a function. Look up its argument in a TIP and recur.
instance (Monad m, HOccurs arg db, TransTIPM m op db)
=> TransTIPM m (arg -> op) db where
ttipM f db = ttipM (f (hOccurs db)) db
-} -- !Simple
-- {- -- !TF
-- The TF implementation. When specifying the operation to perform over
-- a TIP, we can leave it polymorphic over the monad. The type checker
-- will instantiate the monad based on the context.
class Monad m => TransTIPM m op db where
ttipM :: op -> db -> m db
-- Check to see if the operation is a computation whose result
-- is in the TIP. The type variable m' of the kind *->* below
-- can be instantiated either to a monad type constructor, or (arg->).
instance (Monad m, HMember op db b, TransTIPM' b m (m' op) (TIP db))
=> TransTIPM m (m' op) (TIP db) where
ttipM = ttipM' (proxy :: Proxy b)
class Monad m => TransTIPM' (b :: Bool) m op db where
ttipM' :: Proxy b -> op -> db -> m db
-- If op is found in a TIP, update the TIP with op.
-- The type variable m' must be equal to the type of the monad
-- in which the final result is reported.
instance (Monad m, m ~ m',
HTypeIndexed db, HUpdateAtHNat n op db, HUpdateAtHNatR n op db ~ db, HType2HNat op db n)
=> TransTIPM' True m (m' op) (TIP db) where
ttipM' _ op db = do
op' <- op
return $ tipyUpdate op' db
-- If op is not found in a TIP, it must be a function. Look up
-- its argument in a TIP and recur.
instance (Monad m, HOccurs arg db, TransTIPM m op db)
=> TransTIPM' False m (arg-> op) db where
ttipM' _ f db = ttipM (f (hOccurs db)) db
-- -} -- !TF
main :: IO ()
main = do
mapM_ putStrLn [show tip1, show tip2, show tip3, show tip4,
show tip5, show tip5']
tip2 <- tip6
putStrLn $ "tip2 is" ++ show tip2
return ()