Skip to content

Instantly share code, notes, and snippets.

@gfarrell
Last active March 2, 2023 22:12
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save gfarrell/d24c50ed2f7a054104e417d7b58a65d2 to your computer and use it in GitHub Desktop.
Save gfarrell/d24c50ed2f7a054104e417d7b58a65d2 to your computer and use it in GitHub Desktop.
Explaining functional dependencies in Haskell
{- stack script --resolver lts-20.5 -}
-- @FunctionalDependencies@ is a useful extension allowing us to resolve the
-- ambiguity inherently arising in multi-parameter typeclasses. Such typeclasses
-- can be useful, but sometimes unergonomic as it can be impossible to solve for
-- certain type variables.
--
-- For a multiparam typeclass @X@ over two parameters @a@, @b@, there is a
-- potentially infinite combination of @a@s and @b@s. This means that if I
-- have a member function @f :: X a b => a -> b@ then I can't know the type
-- @b@ because there are infinite possible combinations, and it's a free
-- variable (since I haven't constrained it at the call site). By contrast,
-- a member function @g :: X a b => a -> b -> Bool@ is not ambiguous because
-- @a@ and @b@ are constrained at the call site if I specify, however, that
-- @a -> b@ in my typeclass definition, then @b@ is no longer ambiguous, and is
-- uniquely determined by @a@, which makes it possible to write my function
-- @f :: X a b => a -> b@, since @b@ is now a known type (determined by @a@).
--
-- This file does not compile, because it is supposed to demonstrate the problem
-- that this solves, and the extra constraint it places on possible instances.
{-# LANGUAGE FunctionalDependencies #-}
class X a b where
f :: a -> b
g :: a -> b -> Bool
class X' a b | a -> b where
f' :: a -> b
g' :: a -> b -> Bool
data AlphaBet = Aye | Bee deriving Show
data Bin = Zero | One deriving Show
data Nib = Orez | Eno deriving Show
instance X AlphaBet Bin where
f Aye = Zero
f Bee = One
g Aye Zero = True
g Bee One = True
g _ _ = False
instance X' AlphaBet Bin where
f' Aye = One
f' Bee = Zero
g' Aye One = True
g' Bee Zero = True
g' _ _ = False
-- The following instance is now illegal because there are conflicting
-- functional deps
instance X' AlphaBet Nib where
f' Aye = Eno
f' Aye = Orez
g' Aye Eno = True
g' Bee Orez = True
g' _ _ = False
-- Now try to use this
main :: IO ()
main = do
putStrLn "Attempting to use X a b (without functional dependencies"
print $ f Aye -- typecheck error: ambiguous type variable
print (f Aye :: Bin) -- you have to add a type annotation to resolve this
print $ g Aye Zero
putStrLn "Attempting to use X' a b (with functional dependencies"
print $ f' Aye
print $ g' Aye Zero
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment