Last active
March 2, 2023 22:12
-
-
Save gfarrell/d24c50ed2f7a054104e417d7b58a65d2 to your computer and use it in GitHub Desktop.
Explaining functional dependencies in Haskell
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{- 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