The beauty of functional programming is that you can parameterize just about anything, so long as your syntax is valid!
For example:
length : List String -> List Int
length vals =
case vals of
[] -> []
v :: vs -> String.length v :: length vs
Lets parameterize that over the String.length
function:
length : (String -> Int) -> List String -> List Int
length fn vals =
case vals of
[] -> []
v :: vs -> fn v :: length vs
But then we realize there is nothing making specific use of String
or Int
in this, so we arrive at:
map : (a -> b) -> List a -> List b
map fn vals =
case vals of
[] -> []
v :: vs -> fn v :: length vs
Take any function, extract a syntactically valid expression from it and make it a parameter, get creative with that idea, and in a nutshell you have what is so wonderful about functional programming.
So here is what I did today to parameterize some tests that I am writing:
I created a record that captures the API of Dict
, and put it in a module that has a whole bunch of tests around the expected behaviour of Dict
.
[https://github.com/elm-scotland/elm-tries/blob/master/tests/DictIface.elm]
type alias IDict comparable v dict b dictb result =
{ empty : dict
, singleton : comparable -> v -> dict
, insert : comparable -> v -> dict -> dict
, update : comparable -> (Maybe v -> Maybe v) -> dict -> dict
, remove : comparable -> dict -> dict
, isEmpty : dict -> Bool
, member : comparable -> dict -> Bool
, get : comparable -> dict -> Maybe v
, size : dict -> Int
, keys : dict -> List comparable
, values : dict -> List v
, toList : dict -> List ( comparable, v )
, fromList : List ( comparable, v ) -> dict
, map : (comparable -> v -> b) -> dict -> dictb
, foldl : (comparable -> v -> b -> b) -> b -> dict -> b
, foldr : (comparable -> v -> b -> b) -> b -> dict -> b
, filter : (comparable -> v -> Bool) -> dict -> dict
, partition : (comparable -> v -> Bool) -> dict -> ( dict, dict )
, union : dict -> dict -> dict
, intersect : dict -> dict -> dict
, diff : dict -> dictb -> dict
, merge :
(comparable -> v -> result -> result)
-> (comparable -> v -> b -> result -> result)
-> (comparable -> b -> result -> result)
-> dict
-> dictb
-> result
-> result
}
Then multiple implementations of that ‘interface’ were written, one that wraps Dict
and one that wraps the data structure I am working on, which is Trie
. The Dict tests are just to confirm the expected behaviour against the Dict, and the Trie tests are against the Trie API which can be thought of as an extension of Dict. The same tests are run over both data structures using the same code, by some creative test parameterization.
[https://github.com/elm-scotland/elm-tries/blob/master/tests/DictTest.elm]
[https://github.com/elm-scotland/elm-tries/blob/master/tests/TrieTest.elm]