Search code examples
curry

In Curry, how to get inverse reverse function to terminate?


I'm using the PAKCS Curry compiler, version 3.3.0.

Here's a naive list reversal function in Curry (it works in Haskell too):

rev :: [a] -> [a]
rev [] = []
rev (x : xs) = (rev xs) ++ [x]

Let's try to invert the function, i.e. find a list l whose reversal is [1..5]:

> rev l =:= [1 .. 5] where l free
{l=[5,4,3,2,1]} True
...hang...

It's nice that Curry can solve the equation. However, it goes into an infinite loop looking for more solutions.

Here's an equivalent predicate in Prolog:

rev([], []).
rev([X | L], M) :- rev(L, LR), append(LR, [X], M).

The Prolog predicate also fails to terminate in the reverse direction:

?- rev(L, [1, 2, 3, 4, 5]).
L = [5, 4, 3, 2, 1] ;
...hang...

In Prolog, I can help termination by adding the constraint that L and its inverse have the same length:

rev([], []).
rev([X | L], M) :- same_length([X | L], M), rev(L, LR), append(LR, [X], M).

With that extra constraint, the predicate terminates in both directions.

In Curry, it is possible to add a similar constraint, or otherwise help the query terminate?

I tried defining an inverse function with a similar same_length constraint:

same_length :: [a] -> [b] -> Bool
same_length [] [] = True
same_length (_ : _) [] = False
same_length [] (_ : _) = False
same_length (_ : xs) (_ : ys) = same_length xs ys

rev :: [a] -> [a]
rev [] = []
rev (x : xs) = (rev xs) ++ [x]

rev2 :: Data a => [a] -> [a]
rev2 b@(rev a) | same_length a b = a

However it doesn't help: 'rev2 [1 .. 5]' also finds a single solution, then fails to terminate.


Solution

  • Your idea to take the length of the list into account is perfectly fine. However, in your definition of rev2, the functional pattern b@(rev a) still is evaluated before the guard same_length a b is checked and, thus, your evaluation does not terminate after the first solution is found.

    The simplest solution I can think of is to move the same_length constraint at the beginning of the guard, while you have the unification constraint at the end - combined using (&&).

    revInv xs | same_length xs ys && rev ys =:= xs = ys where ys free
    

    To further improve the inverse definition w.r.t. non-strictness regarding the elements of the list (e.g., the definition above fails if one of the elements is a failure), you can even use the non-strict unification operator =:<= instead of the strict one =:=.

    revInv' xs | same_length xs ys && rev ys =:<= xs = ys where ys free
    

    For comparison, consider the following two expressions.

    > head (revInv [1, failed, 2])
    *** No value found!
    > head (revInv' [1, failed, 2])
    2