input/output is a side effect.
pure functions are not supposed to have side effects
IO is a monad to sequence side effects. It also allows us to push effects to the “edges” of our program.
putStrLn :: String -> IO () getLine :: IO String
Lots of languages have “unit test” frameworks/libraries. (jUnit, unittest(python), pytest, cppunit, …)
Unit tests pertain to one small piece of code – one function or one class. A lot of unit tests come down to comparing actual results with expected results.
Straightforward testing library for Haskell is HUnit.
Exercise: we want a function that can determine whether a string is a “palindrome”. Example: “racecar”
Write one or more tests first.
test_racecar = isPalindrome "racecar" @?= True test_car = isPalindrome "car" @?= False test_abba = isPalindrome "abba" @?= True test_abcba = isPalindrome "abcba" @?= True test_abcdba = isPalindrome "abcdba" @?= False test_wspaces = isPalindrome "mr owl ate my metal worm" @?= True test_wpunct = isPalindrome "ab.cd,c!ba" @?= True test_alphacase = isPalindrome "abBA" @?= True test_numbers = isPalindrome "a2bb3a" @?= False
actual @?= expected
expected @=? actual
main = runTestTT $ TestList [ TestLabel "racecar" (TestCase test_racecar) , TestLabel "car" (TestCase test_car) , TestLabel "abba" (TestCase test_abba) , TestLabel "abcba" (TestCase test_abcba) , TestLabel "abcdba" (TestCase test_abcdba) , TestLabel "wspaces" (TestCase test_wspaces) , TestLabel "wpunct" (TestCase test_wpunct) , TestLabel "alphacase" (TestCase test_alphacase) , TestLabel "numbers" (TestCase test_numbers) ]
Idea: instead of manually coming up with specific examples to use in tests, we state a general property (using code) and the test system comes up with LOTS of randomly-generated examples and checks whether the property holds (is true).
Original property-based testing tool was QuickCheck. It was developed for Haskell and Erlang by John Hughes. The idea was so compelling, many other languages started adopting it.
Let’s try an example with sorting.