I find that to be a very succinct, readable definition. I probably would have written it too. Unfortunately it's wrong!
One of the more tedious requirements of beautiful code is that it be correct, so let's set up some really quick testing using QuickCheck. Since you've been reimplementing functions from
base QuickCheck can generate a bunch of random test data and throw it at both the original function and your reimplementation and verify that the outputs match.
We'll start by defining a property that says the output from
\\ should always be the same.
prop_equal :: (Eq a) => [a] -> [a] -> Bool
prop_equal xs ys = setDifference xs ys == xs \\ ys
We can use this property ourselves in an interactive session, but that gets tedious fast. Instead, call
quickCheck with the property and it will generate arbitrary data to test it with.
> :m +Test.QuickCheck
> quickCheck prop_equal
*** Failed! Falsifiable (after 9 tests and 4 shrinks):
Aha! QuickCheck has determined that our property doesn't always hold. Each line printed after "Failed!" corresponds to an argument to the property. This might appear a little confusing due to the use of
() (the unit or void type). Because our property was polymorphic, GHC picked a default. You can specialize the property to get output with other types, try
quickCheck (prop_equal :: String -> String -> Bool). (You may have gotten slightly different output, that is okay and expected due to the generation of arbitrary data having been random.)
By itself this might not tell us enough though, why did these two arguments produce a failure? You can get all of the cases that QuickCheck tried by using
> verboseCheck (prop_equal :: String -> String -> Bool)
... pages ...
This will print a whole pile of text with various passing and failing cases until it gives up trying to shrink the test case. Run verboseCheck a couple of times and eyeball the output, you should be able to notice that it always reduces the failure to something like the following.
If you explore these examples manually in the repl or simulate them by hand (or imagination) you'll see that
setDifference removes every instance of the elements from
xs instead of just the first as
\\ does. Now armed with the fact that the implementation is wrong, and knowing what's wrong with it we can head back to the drawing board to fix
For a full exploration of QuickCheck check out Real World Haskell's chapter on QuickCheck.