💾 Archived View for idiomdrottning.org › fancy-defines captured on 2023-09-28 at 17:01:58. Gemini links have been rewritten to link to archived content
⬅️ Previous capture (2021-12-03)
➡️ Next capture (2024-02-05)
-=-=-=-=-=-=-
Update: Now part of brev-separate.
I’m a little bit late to the party on this one but I just found out that the following “curried” define works in Chicken, Guile and Racket, and maybe some others too.
(define ((add a) b) (+ a b)) (map (add 3) '(1 3 6))
I love it!
That’s the perfect kinda shortcut. Saves tokens and adds clarity.
I’ve written these kinds of definitions all the time, except that I’ve written them as:
(define (add a) (lambda (b) (+ a b)))
I went ahead and changed that to the new curried way of doing it into my two most recent projects. Found five places in just those two. My SLOC-budget is grateful♥
I found it in kind of a weird way, actually. I was working on a blog post about how much it sucks that the language didn’t have this, and while writing it I accidentally evaled it, and it worked, and I was happy and then I went and scoured the docs and learned that yes, it wasn’t my memory that was at fault, it’s not R5RS and just forgotten by me, but it is in some of the implementations.
I love seeing the language add this. I’m definitively not onboard with the convention to use [] and {} instead of (), I love my normal round parens, although given that I have pretty sick values on defface paren-face in .emacs, maybe I don’t love them that much.
This kind of “curried define”, though, I’m really on board with. Encapsulating patterns is what it’s all about, darlings!
Wasamasa wrote in, saying it’s in a draft SRFI.
That’s awesome.♥
The curried define also works for lexical closures. (Side effects and state, baby.♥)
(define counter (let ((x 0)) (lambda () (inc! x))))
Can now be written like this:
(define ((make-counter x)) (inc! x)) (define counter (make-counter 0))
Not exactly shorter, although, in some situations it’s the perfect solution: when you need more than one counter, or, when you are passing the value of counter anyway rather than its existing reference.
Other times, I’d rather have something like this:
(define-closure (x 0) (counter) (inc! x))
You could have any amount of bindings, alternated. I never understood the purp of the paren pairs there, although I’m not the sharpest tool in the proverbial. Using the define-ir-syntax* from the other day, it could look something like this:
(define-ir-syntax* define-closure ((define-closure bindings head body ...) (match (chicken.syntax#expand-curried-define head body '()) ((define name body) `(define ,name (let ,(let desc ((lis bindings)) (cons (list (car lis) (cadr lis)) (if (null? (cddr lis)) '() (desc (cddr lis))))) ,body))))))
Seems to work just fine.♥
(define-closure (x 5 y 10) (jolly) (inc! x) (dec! y) (list x y)) (list (jolly) (jolly) (jolly))
⇒ ((6 9) (7 8) (8 7))
Matchable has match-lambda and match-lambda* as shortcuts, and they’re fantastic, but let’s also add a match-define as a shortcut on top of them. Maybe this is being penny-wise at this point...
How about these sorta Haskell-style semantics?
(match-define ((foo pat1 ...) body...) ((foo pat2 ...) body...))
Here’s a stab at implementing them:
(define-ir-syntax match-define `(define ,(caaadr exp) (match-lambda* ,@(map (lambda (el) (cons (cdar el) (cdr el))) (cdr exp)))))
Seems to do the job:
(match-define ((my-map proc ()) '()) ((my-map proc (x . xs)) (cons (proc x) (my-map proc xs)))) (my-map - (iota 4))
⇒ (0 -1 -2 -3)
Works with curried define too, of course:
(match-define (((my-map proc) ()) '()) (((my-map proc) (x . xs)) (cons (proc x) ((my-map proc) xs)))) ((my-map -) (iota 4))
⇒ (0 -1 -2 -3)
(map (my-map -) '((1 2 3) (10 20 30) (100 200 300)))
⇒ ((-1 -2 -3) (-10 -20 -30) (-100 -200 -300))
(Although this implementation only destructures the outer argument list. It would be awesome to be able to pattern match on every level.)
And maybe this is also useful:
(define-for-syntax caaaddr (o caaar cddr)) (define-ir-syntax match-define-closure `(define ,(caaaddr exp) (let ,(let desc ((lis (cadr exp))) (cons (list (car lis) (cadr lis)) (if (null? (cddr lis)) '() (desc (cddr lis))))) (match-lambda* ,@(map (lambda (el) (cons (cdar el) (cdr el))) (cddr exp)))))) (match-define-closure (x 0) ((counter) (inc! x)) ((counter 'reset) (set! x 0))) (list (counter) (counter) (counter 'reset) (counter) (counter))
⇒ (1 2 #<unspecified> 1 2)
Here is another example, the arity table from SDFF:
(match-define-closure (ht (make-hash-table)) ((arity proc) (hash-table-ref ht proc)) ((arity proc a) (hash-table-set! ht proc a)))
Or, if you want a more general way to do it (bringing together closures, matching, and a curried define all in one):
(match-define-closure (ht (make-hash-table)) (((call-table) key) (hash-table-ref ht key)) (((call-table) key val) (hash-table-set! ht key val))) (define arity (call-table)) (define color (call-table)) (arity cons 2) (color cons 'blue) (map (cut <> cons) (list arity color))