0

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.

Adam Dingle
  • 3,114
  • 1
  • 16
  • 14

1 Answers1

1

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
Dharman
  • 30,962
  • 25
  • 85
  • 135
fte
  • 26
  • 1
  • I confirmed that your function revInv finds a solution and then terminates. Great - thanks for the quick and helpful reply. – Adam Dingle Mar 19 '21 at 11:35
  • However, I have a followup question. Notice that in the Prolog code in my question I included the same_length constraint in the rev predicate itself. In Curry, would it similarly be possible to embed this constraint into the rev function, so that the query 'rev l =:= [1 .. 5] where l free' would work directly? – Adam Dingle Mar 19 '21 at 11:48
  • I do not see how. The problem is that the inner `rev l` has no knowledge of the outer `(=:= [1 .. 5])` application. In the Prolog version, the predicate can make use of information from its result. The Curry function, however, cannot. Of course, you could implement a relation in Curry to overcome this issue: `rev'' [] [] = True; rev'' (x:l) m | same_length (x:l) m && rev'' l lr && lr ++ [x] =:= m = True where lr free`. Like the Prolog version, this version works in both directions, i.e., both `rev'' l [1,2,3] where l free` and `rev'' [1,2,3] l where l free` yield one result. – fte Mar 19 '21 at 13:27
  • Thanks very much for the additional explanation. Although I know both Haskell and Prolog pretty well, I'm still getting my bearings in Curry and trying to figure out what is possible. Your example of translating a Prolog relation into Curry is informative. – Adam Dingle Mar 19 '21 at 14:05