This is a nice example use of ImplicitParameters

Basically what you should see, that there are services all around, but they passed implicitly between layers


In [1]:
{-# LANGUAGE ImplicitParams #-}

Services


In [2]:
data DatabaseService = DatabaseService { getData :: IO [String]}
type NameFilterService = String -> Bool
type NameGetterService = IO [String]

-- Notice, that there are no type definition, since we should define all implicit parameters type
getNames = filter ?nameFilterService <$> getData ?database

-- Notice that there is no knowledge in this layer about the creation of the services
concatenateNames = concat <$> ?getNames

Production


In [3]:
import Data.Char(isUpper)

readingFromFile :: IO [String]
readingFromFile = return ["kutya", "Andy", "macska", "James"]

createGetNames = let ?nameFilterService = isUpper . head
                 in getNames
          

main = do let ?database = DatabaseService {getData=readingFromFile}
          let ?getNames = createGetNames
          concatenatedNames <- concatenateNames
          print concatenatedNames

main


"AndyJames"

Test


In [4]:
data Result = PASSED | FAILED deriving(Show)
assert a = if a then return PASSED  else return FAILED

test = mapM_ (>>=print) [ createTestCase ["Kakas","kutya","barany"], createTestCase []
                        , testConcat [] [], testConcat ["A", "B"] "AB"]


createTestCase test_data = do let ?database = DatabaseService {getData=return test_data}
                                  ?nameFilterService = const True
                              names <- getNames 
                              assert (names == test_data)

-- Notice that we only have to mock one service, which in production uses much more
testConcat input output = do let ?getNames = return input
                             result <- concatenateNames
                             assert (result == output)
                             

test


PASSED
PASSED
PASSED
PASSED