Arc Forumnew | comments | leaders | submitlogin
5 points by rntz 5751 days ago | link | parent

foldl and foldr exist in lib/util.arc on anarki. Unlike the above implementation, they are both tail-recursive:

    (def flip (f)
      "Flips the order of arguments of 'f. eg: ((flip cons) 1 2) -> (2 . 1)"
      (fn (a b) (f b a)))
    
    (def foldl (f v l)
      (if l (foldl f (f v car.l) cdr.l) v))

    (def foldr (f v l)
      (foldl flip.f v rev.l))
Note that the order-of-arguments to the function passed to lib/util's version of foldl follows that of Haskell rather than SML. This is also the order used on Wikipedia (http://en.wikipedia.org/wiki/Foldl).

On what grounds (benchmarks, examples, etc) do you claim your implementations are faster than the original implementations? From merely looking at the code, it seems to me that map1 looks just like your fold version, inlined.



2 points by skenney26 5751 days ago | link

The tests weren't exhaustive, just simple comparison tests:

  (time (repeat 1000 (map1 [* _ 3] '(1 2 3 4 5))))
Comparing the different implementations of map1, the one using the definition of fold (foldr) at the top of the page was the fastest, followed by Arc's version, followed by Anarki's version (using the definitions of flip, foldl and foldr in rntz's comment).

These informal tests were repeated on best and rev with the same results. I honestly don't know why it's faster.

-----

2 points by rntz 5751 days ago | link

Interesting. I get the same results, even when I modify the test slightly to run on lists of different lengths:

    (def testmap (ntests map) (time (for i 0 ntests (map [* _ 3] (range 0 ntests)))))
No matter the number of tests, though, the difference is only a millisecond or so, which is negligible as the size of the input increases or if the mapped function does nontrivial work.

However, I've also tried the non-tail-recursive fold on very large lists, and it doesn't appear to blow the stack. mzscheme probably does something to prevent this from happening, like allocating "stack" frames on the heap (a standard technique for implementing call/cc without stack-copying, and if your GC is good it's not much of a penalty). Given this, it seems more defensible that arc.arc's 'map1 et al are non-tail-recursive. I think I may change lib/util's foldr to use the OP's implementation.

-----

3 points by rkts 5751 days ago | link

The difference is the call to (no xs). Swap the then/else cases in map1 and it runs as fast as your version.

-----