Skip to content

Commit 971afa9

Browse files
authored
Add a transform rule making (++) for List tail-recursive. (#1888)
1 parent 34c7209 commit 971afa9

File tree

6 files changed

+84
-17
lines changed

6 files changed

+84
-17
lines changed

libs/base/Data/List.idr

Lines changed: 30 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -346,15 +346,6 @@ public export
346346
replaceOn : Eq a => a -> a -> List a -> List a
347347
replaceOn a = replaceWhen (== a)
348348

349-
public export
350-
reverseOnto : List a -> List a -> List a
351-
reverseOnto acc [] = acc
352-
reverseOnto acc (x::xs) = reverseOnto (x::acc) xs
353-
354-
public export
355-
reverse : List a -> List a
356-
reverse = reverseOnto []
357-
358349
||| Construct a list with `n` copies of `x`.
359350
||| @ n how many copies
360351
||| @ x the element to replicate
@@ -739,6 +730,36 @@ consInjective : forall x, xs, y, ys .
739730
the (List a) (x :: xs) = the (List b) (y :: ys) -> (x = y, xs = ys)
740731
consInjective Refl = (Refl, Refl)
741732

733+
reverseReverseOnto : (l, r : List a) -> reverse (reverseOnto l r) = reverseOnto r l
734+
reverseReverseOnto _ [] = Refl
735+
reverseReverseOnto l (x :: xs) = reverseReverseOnto (x :: l) xs
736+
737+
||| List reverse applied twice yields the identity function.
738+
export
739+
reverseInvolutive : (xs : List a) -> reverse (reverse xs) = xs
740+
reverseInvolutive = reverseReverseOnto []
741+
742+
consReverse : (x : a) -> (l, r : List a) -> x :: reverseOnto r l = reverseOnto r (reverseOnto [x] (reverse l))
743+
consReverse _ [] _ = Refl
744+
consReverse x (y::ys) r
745+
= rewrite consReverse x ys (y :: r) in
746+
rewrite cong (reverseOnto r . reverse) $ consReverse x ys [y] in
747+
rewrite reverseInvolutive (y :: reverseOnto [x] (reverse ys)) in
748+
Refl
749+
750+
consTailRecAppend : (x : a) -> (l, r : List a) -> tailRecAppend (x :: l) r = x :: (tailRecAppend l r)
751+
consTailRecAppend x l r
752+
= rewrite consReverse x (reverse l) r in
753+
rewrite reverseInvolutive l in
754+
Refl
755+
756+
||| Proof that `(++)` and `tailRecAppend` do the same thing, so the %transform
757+
||| directive is safe.
758+
tailRecAppendIsAppend : (xs, ys : List a) -> tailRecAppend xs ys = xs ++ ys
759+
tailRecAppendIsAppend [] ys = Refl
760+
tailRecAppendIsAppend (x::xs) ys =
761+
trans (consTailRecAppend x xs ys) (cong (x ::) $ tailRecAppendIsAppend xs ys)
762+
742763
||| The empty list is a right identity for append.
743764
export
744765
appendNilRightNeutral : (l : List a) -> l ++ [] = l
@@ -768,14 +789,6 @@ revAppend (v :: vs) ns
768789
rewrite appendAssociative (reverse ns) (reverse vs) [v] in
769790
Refl
770791

771-
||| List reverse applied twice yields the identity function.
772-
export
773-
reverseInvolutive : (xs : List a) -> reverse (reverse xs) = xs
774-
reverseInvolutive [] = Refl
775-
reverseInvolutive (x :: xs) = rewrite revOnto [x] xs in
776-
rewrite sym (revAppend (reverse xs) [x]) in
777-
cong (x ::) $ reverseInvolutive xs
778-
779792
export
780793
dropFusion : (n, m : Nat) -> (l : List t) -> drop n (drop m l) = drop (n+m) l
781794
dropFusion Z m l = Refl

libs/prelude/Prelude/Types.idr

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -389,6 +389,26 @@ namespace List
389389
length [] = Z
390390
length (x :: xs) = S (length xs)
391391

392+
public export
393+
reverseOnto : List a -> List a -> List a
394+
reverseOnto acc [] = acc
395+
reverseOnto acc (x::xs) = reverseOnto (x::acc) xs
396+
397+
public export
398+
reverse : List a -> List a
399+
reverse = reverseOnto []
400+
401+
||| Tail-recursive append. Uses of (++) are automatically transformed to
402+
||| this. The only reason this is exported is that the proof of equivalence
403+
||| lives in a different module.
404+
public export
405+
tailRecAppend : (xs, ys : List a) -> List a
406+
tailRecAppend xs ys = reverseOnto ys (reverse xs)
407+
408+
-- Always use tailRecAppend at runtime. Data.List.tailRecAppendIsAppend
409+
-- proves these are equivalent.
410+
%transform "tailRecAppend" (++) = tailRecAppend
411+
392412
public export
393413
Functor List where
394414
map f [] = []

tests/Main.idr

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,7 @@ nodeTests = MkTestPool "Node backend" [] (Just Node)
271271
, "idiom001"
272272
, "integers"
273273
, "fix1839"
274+
, "tailrec_libs"
274275
]
275276

276277
vmcodeInterpTests : IO TestPool

tests/node/tailrec_libs/expected

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
10001

tests/node/tailrec_libs/run

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
rm -rf build
2+
3+
$1 --cg node --no-banner --no-color --console-width 0 tailrec.idr -x main

tests/node/tailrec_libs/tailrec.idr

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
{-
2+
3+
Test that some library functions don't cause "Maximum call stack size exceeded"
4+
errors.
5+
6+
So far, only Prelude.List.++ is tested.
7+
8+
-}
9+
10+
module Main
11+
12+
-- Until length and replicate are tail recursive, we roll our own for this test.
13+
14+
lengthPlus : Nat -> List a -> Nat
15+
lengthPlus n [] = n
16+
lengthPlus n (x::xs) = lengthPlus (S n) xs
17+
18+
tailRecLength : List a -> Nat
19+
tailRecLength = lengthPlus Z
20+
21+
replicateOnto : List a -> Nat -> a -> List a
22+
replicateOnto acc Z x = acc
23+
replicateOnto acc (S n) x = replicateOnto (x :: acc) n x
24+
25+
tailRecReplicate : Nat -> a -> List a
26+
tailRecReplicate = replicateOnto []
27+
28+
main : IO ()
29+
main = putStrLn $ show $ tailRecLength $ tailRecReplicate 10000 () ++ [()]

0 commit comments

Comments
 (0)