💾 Archived View for michal_atlas.srht.site › posts › continuation-ftw.gmi captured on 2024-08-31 at 12:21:53. Gemini links have been rewritten to link to archived content
⬅️ Previous capture (2024-02-05)
-=-=-=-=-=-=-
(use-modules (srfi srfi-26) ;; Cut (ice-9 ftw) ;; File methods (ice-9 control)) ;; %/abort
When you want a list of regular files[a] in a directory in Guile Scheme, you might want to recursively list everything in a tree and check the files in there. And you might see scandir which works nicely, and has an incredibly simple interface, however it doesn’t recurse. Aaand it returns . and .. which you need to throw away and it doesn’t show the entire path, so if you’d want to recurse yourself you need to deal with that as well.
(define (recdir file) (append-map (compose (lambda (f) (if (file-is-directory? f) (cons f (recdir f)) (list f))) (cut string-append file "/" <>)) (cddr (scandir file))))
This is not really too bad, but I don’t like, it… just seems off, the fact that I have to traverse by myself, and mainly that I’m ignoring with cddr, am I sure that .. will always show up in every dir? It also can’t deal with some filesystem edge cases like loops with symlinks.
It’s not too bad ofc, but there’s probably a better way. There’s file-system-tree, which recurses, but you still have to parse the output, which isn’t trivial, isn’t too bad but we can do better.
ftw is a brilliant little function, which just calls a lambda with all the files under a path.
You can check what the arguments are like this: scheme (ftw "/tmp" (compose (const #t) peek))
Ftw uses the return to see if you want to continue the walk That’s why the const is in there to just let it continue
Now you’ll see that it really does traverse everything, and it’s a library function so I believe it over my crude string appending. The issue is there’s not really any way to convince ftw to give you a list of those files, which is what I need.
If you suggested just using set! and consing it to a variable outside the lambda then bonk.
Continuations to the rescue, you can estabilish a prompt around the ftw call and then keep wrapping the continuation in conses.
(% (begin (ftw "/tmp" (lambda (path _ type) (abort (lambda (k) (cons path (k #t)))))) '()))
The begin is there because I need the last continuation to return an empty list, since I’m consing onto it and want the output to be well formed.
The default prompt handler accepts one argument and passes the captured continuation to it inside a new prompt. So you can just pass a lambda to abort as a sort of handler that takes k and use that inside it.
Abort theoretically abandons the current calculation and jumps up to the point of %, however it’s awesome that it lets you go, “oh and finish the calculation as if the abort never got called and tell me what it returns”, which is what (k #t) does. And continuing the calculation in each step is to just continue to the next file, abandon it again returning the list, that just so happens to contain the rest of the evaluation in cdr, getting finally to one point where there’s no more files and ftw exits and properly returns '() from the whole body, which is captured by the previous abort, which returns and returns for the value to be used by the second to last abort, etc.
In this case you can imagine the abort as a finalization function, that takes whatever you do to k and wraps the whole % expression with it. The lambda has to return #t so ftw continues, so the return of abort has to be #t, which is why k is invoked with it.
This returns a perfect list of all the files, but the problem required only regular files, so to filter those out, you can only call abort under some condition. scheme (if (eq? type 'regular) <abort> #t) Should do the trick.