0
\$\begingroup\$

Let's say you have a simple pure function that applies a discount of 30% if the total price of a list of Items is over 30.00 (let's not delve into the fact that I'm using a Float to indicate the price):

promoGet30PercentOff :: [Item] -> Float
promoGet30PercentOff xs
 | total > 30.0 = total * 0.7
 | otherwise = total
 where total = sum $ map itemPrice xs

and Item and itemPrice are defined as follows:

data Item = Apple | Banana deriving Show
itemPrice :: Item -> Float
itemPrice x =
 case x of
 Apple -> 5.9
 Banana -> 3.0

I'd now like to test promoGet30PercentOff at the boundary. As I come from Python, my natural reaction would be to mock Item and create some that are priced at 30.0, 30.1 and 0.0, for instance.

I believe, this is not possible in Haskell (but correct me if I'm wrong). How would you go about it then?

asked Nov 7, 2022 at 15:05
\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

You can untangle your promo function from the Item data type

promoGet30PercentOffWith :: (a -> Float) -> [a] -> Float
promoGet30PercentOffWith price xs
 | total > 30.0 = total * 0.7
 | otherwise = total
 where total = sum $ map price xs
promoGet30PercentOff :: [Item] -> Float
promoGet30PercentOff = promoGet30PercentOffWith itemPrice

Because promoGet30PercentOffWith is polymorphic, there is a free theorem that all functions of its type satisfy:

promoGet30PercentOffWith f xs = promoGet30PercentOffWith id (map f xs)

This means that the general behavior of that function is entirely determined by its specialized behavior when the first argument is id---so the list argument has type [Float]. Thus, mocking items with artificial prices is as simple as giving the list of prices to the function.

shouldPromo :: [Float] -> IO ()
shouldPromo xs = promoGet30PercentOffWith id xs === sum xs * 0.7
shouldNotPromo :: [Float] -> IO ()
shouldNotPromo xs = promoGet30PercentOffWith id xs === sum xs
(===) :: Eq a => a -> a -> IO ()
x === y = if x == y then return () else error "assertion failed"
test :: IO ()
test = do
 shouldPromo [30.1]
 shouldNotPromo [30.0]
 shouldNotPromo [0]
answered Nov 12, 2022 at 10:57
\$\endgroup\$
0

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.