{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE OverlappingInstances #-}
module Main where
import qualified Data
.Char {-
Define a Haskell class that describes how OO methods work.
Basically an OO method takes a number of input variables, the object itself, and returns:
1. Some output AND/OR
2. A modified object.
This is encapulated in the class C below and the function 'ap':
-}
class C c m i o | m -> i, m -> o where
ap :: m -> i -> c -> (o, c)
{-
Lets make a data type that represents a user.
Just first name and last name for the moment
-}
aUser = User "Alice" "Smith"
{- Now, define a GetFirstName method -}
-- We need a dummy data type to put in the 'm' part of the type class.
data GetFirstName = GetFirstName
-- A get method takes no input and returns a string
-- The implementation. We obviously ignore the dummy method, and the input,
-- and return the first name as output, and the object unchanged.
instance C User GetFirstName
() String where ap _ _ u@(User fn _) = (fn, u)
-- Here's a method that make things easier for our users.
{- Lets do a similar thing with PutFirstName -}
data PutFirstName = PutFirstName
instance C User PutFirstName
String () where ap _ fn (User _ ln) = ((), User fn ln)
putFirstName fn u
= snd $ ap
(undefined :: PutFirstName
) fn u
{-
We could now do the following to print "Bob",
but I've commented it out because I want to do something further:
-}
--main = print $ getFirstName $ putFirstName "Bob" aUser
{- Here's a generic way to extend types -}
data Extend base extension = Extend base extension
-- An existing method on a extended object has the same input and output as the base object
-- If we have a method defined on the base object, it is defined on the extended object also
instance {-# OVERLAPPABLE #-} C base m i o => C (Extend base ext) m i o where
ap m in_data (Extend b e) = let (r, nb) = ap m in_data b in (r, Extend nb e)
{- We're going to extend users by adding Age -}
{- Here's our new type -}
type ExtendedUser = Extend User Age
aExtendedUser = Extend aUser (Age 42)
{- Lets define a new method -}
data GetAge = GetAge
instance C ExtendedUser GetAge
() Int where ap _ _ x@(Extend _ (Age age)) = (age, x)
instance C ExtendedUser GetFirstName
() String where ap
_ _ u
@(Extend
(User fn
_) _) = (map Data
.Char.toUpper fn
, u
)
main = do
print $ getFirstName
$ putFirstName
"Bob" aUser
print $ getAge
$ aExtendedUser
print $ getFirstName
$ putFirstName
"Bob" aExtendedUser