Search code examples
haskellfunctional-programmingoverloadingtypeclasscustomization-point

How to deal gracefully with customization points of algorithms as well as constraints on their arguments?


As an example, let's take the following algorithm for computing the longest common subsequence of two sequences, copied from Rosetta Code:

longest xs ys = if length xs > length ys then xs else ys
 
lcs [] _ = []
lcs _ [] = []
lcs (x:xs) (y:ys) 
  | x == y    = x : lcs xs ys
  | otherwise = longest (lcs (x:xs) ys) (lcs xs (y:ys))

lcs impicitly assumes that both arguments are of type Eq a => [a]; indeed, if I try to explicitly give the signature lcs :: [a] -> [a] -> [a] an error occurs on the line where x == y is, whereas the signature lcs :: Eq a => [a] -> [a] -> [a] works.

Now let's suppose I have two lists l1 and l2, both of type [(a,b)], and that I want the LCS between them, but in a way that uses the == operator in the definition of lcs only between the snds of each element (obviously b is required to belong to Eq typeclass).

I could provide lcs with not only the two lists, but also with the equality operator, which in the specific example above would be (==) `on` snd. The signature of lcs would then be (a -> a -> Bool) -> [a] -> [a] -> [a].

However, this would force the user to provide the equality operator even in the trivial case that one wants to use a plain (==), and wrapping the (a -> a) argument in a Maybe wouldn't help either.

In other words I feel that in the general case the constraint on the argument via Eq a => is ok, but in other cases one might want to pass a custom equality operator removing that constraint, which makes me thing of function overloading.

How should I go about this?


Solution

  • You simply provide both – a custom-operator version lcsBy, and an Eq a => version which is implemented in terms of that.

    lcsBy :: (a->a->Bool) -> [a] -> [a] -> [a]
    ...
    lcsBy compOp (x:xs) (y:ys) 
     | compOp x y  = ...
     ...
    
    lcs :: Eq a => [a] -> [a] -> [a]
    lcs = lcsBy (==)
    

    This is analogous to how the base library provides both maximum and maximumBy.