{-# OPTIONS_GHC -freduction-depth=1000 #-}
{-# LANGUAGE
FlexibleContexts, FlexibleInstances, FunctionalDependencies,
MultiParamTypeClasses, TypeSynonymInstances,
TypeOperators, UndecidableInstances, TypeFamilies #-}
module T5321FD where
-------- USES FUNCTIONAL DEPENDENCIES -------------
-- Our running example, for simplicity's sake, is a type-level map of a
-- single function. For reference, here is the code for a simple
-- value-level map of a single function.
-- vfoo = id
-- mapfoo (x : xs) = vfoo x : mapfoo xs
-- mapfoo [] = []
-- Because Haskell is a lazy language, this runs in O(n) time and constant stack.
-- We now lift map to the type level, to operate over HLists.
-- First, the basic HList types
infixr 3 :*
data x :* xs = x :* xs deriving Show
data HNil = HNil deriving Show
-- Next, a large boring HList
-- Adds ten cells
addData x = i :* i :* d :* d :* s :*
i :* i :* d :* d :* s :*
x
where i = 1 :: Int
d = 1 :: Double
s = ""
-- Has 70 cells.
sampleData = addData $ addData $ addData $ addData $ addData $
addData $ addData $
HNil
-- Next, a simple polymorphic function to map
class Foo x y | x -> y
where foo :: x -> y
foo = undefined
instance Foo Int Double
instance Foo Double Int
instance Foo String String
------------------------
-- Now, our map
class HMapFoo1 as bs | as -> bs where
hMapFoo1 :: as -> bs
instance (Foo a b, HMapFoo1 as bs) => HMapFoo1 (a :* as) (b :* bs) where
hMapFoo1 (x :* xs) = foo x :* hMapFoo1 xs
instance HMapFoo1 HNil HNil where
hMapFoo1 _ = HNil
-- If we enable the following line, compilation time is ~ 9 seconds.
testHMapFoo1 = hMapFoo1 sampleData
------------------------
class HMapFoo2 acc as bs | acc as -> bs where
hMapFoo2 :: acc -> as -> bs
instance (Foo a b, HMapFoo2 (b :* bs) as res) => HMapFoo2 bs (a :* as) res where
hMapFoo2 acc (x :* xs) = hMapFoo2 (foo x :* acc) xs
instance HMapFoo2 acc HNil acc where
hMapFoo2 acc _ = acc
-- If we enable the following line, compilation time is a much more satisfying ~0.5s.
testHMapFoo2 = hMapFoo2 HNil sampleData
------------------------
-- But wait, there's trouble on the horizon! Consider the following version:
class HMapFoo3 acc as bs | acc as -> bs where
hMapFoo3 :: acc -> as -> bs
instance (HMapFoo3 (b :* bs) as res, Foo a b) => HMapFoo3 bs (a :* as) res where
hMapFoo3 acc (x :* xs) = hMapFoo3 (foo x :* acc) xs
instance HMapFoo3 acc HNil acc where
hMapFoo3 acc _ = acc
-- The only difference between hMapFoo2 and hMapFoo3 is that the order of
-- constraints on the inductive case has been reversed, with the
-- recursive constraint first and the immediately checkable constraint
-- second. Now, if we enable the following line, compilation time rockets
-- to ~6s!
testHMapFoo3 = hMapFoo3 HNil sampleData