{-# LANGUAGE UndecidableInstances, ExistentialQuantification,
              ScopedTypeVariables, Rank2Types #-}

--      Modular arithmetic, due to Dale Thurston

-- Here's a way to mimic dependent types using existential types,
-- illustrated by an implementation of modular arithmetic.  To try it
-- out, load modulus.hs and try something like
--      inModulus (mkModulus (1234567890123::Integer)) (^ 98765432198765) 2
-- to compute 2 to the 98765432198765'th power modulo 1234567890123.

-- The key is the definitions at the top of TypeVal.hs:
--
--   class TypeVal a t | t -> a where
--       -- typeToVal should ignore its argument.
--       typeToVal :: t -> a
--
--   data Wrapper a = forall t . (TypeVal a t) => Wrapper t
--
--   class ValToType a where
--      valToType :: a -> Wrapper a
--
-- `valToType' takes a value `x' and returns a (wrapped version of a)
-- fake value in a new type; from the new type, `x' can be recovered by
-- applying typeToVal.
--
-- This code works under ghc.  It uses existentially quantified data
-- constructors, scoped type variables, and explicit universal
-- quantification.


module Main where

import TypeVal

default (Integer)

main = print (map (inModulus (mkModulus (1234567890123::Integer)) (^98765432198765)) [2..1000])



data Mod s a = Mod {value::a} deriving (Eq, Show)

data Modulus a = forall s. TypeVal a s => Modulus (a -> Mod s a) (Mod s a -> a)

mkModulus :: (ValToType a, Integral a) => a -> Modulus a
mkModulus x = case valToType x of {Wrapper (y :: t) ->
              Modulus normalize (value :: Mod t a -> a)}

normalize :: forall a s. (TypeVal a s, Integral a) => a -> Mod s a
normalize x = (Mod (x `mod` typeToVal (undefined::s)))

inModulus :: Modulus a -> (forall s . TypeVal a s =>  Mod s a -> Mod s a)
                -> a -> a
inModulus (Modulus in_ out) f x = out (f (in_ x))

instance (TypeVal a s, Integral a) => Num (Mod s a) where
    Mod x + Mod y = normalize (x + y)
    Mod x - Mod y = normalize (x - y)
    negate (Mod x) = normalize (negate x)
    Mod x * Mod y = normalize (x * y)
    fromInteger a = normalize (fromInteger a)
