First article of the new year. Slow going is good going, since it means I don't have too many problems to write notes about. I'm briefly revisiting some perennial obsessions today: Scheme, symbols, and point-free programming. When I last explored it, I implemented partial application with macros, but I've since realized that this can be accomplished with any valence through a simple function instead. Much like with Haskell, I'm trying to cultivate a symbolic personal Scheme dialect, though I have been less consistent and successful in this endeavor so far. I will introduce the newest attempt alongside the partial application example.
(define-syntax λ (syntax-rules () ((_ . α) (lambda . α))))
(define-syntax ∃ (syntax-rules () ((_ . α) (let* . α))))
(define-syntax ← (syntax-rules ()
((_ ((α ω) ...)) (begin (define α ω) ...))
((_ . α) (define . α))))
(← ((◇ conc) (⊖ flip) (∘ compose) (id identity)))
(← (? p f g) (λ (α) (if (p α) (f α) (g α))))
Basically synonyms for the most part. ← is overloaded to define multiple values at once if you wish. ? is a point-free if
statement, with very specific branching behavior. I'll have to think up other variations for this concept to be truly useful.
You obviously can't do something like map (+ 1)
in eagerly-evaluated Scheme. That's why lambdas are so much more ubiquitous in the language compared to Haskell. We achieve partial application by wrapping a function in a closure along with some of its arguments. Taken to the logical conclusion, you could elide the final argument of a function of any valence with
(← (⊥ f . α) (λ (ω) (apply f `(,@α ,ω))))
I realize that ⊥ has a different significance in strongly-typed FP, but among the standard Vim digraphs, this one just communicated "delay" the best to me. Maybe it looks like 止?
I'm parsing listening data these days and I have a function that gets rid of the word "tag" in cmus output. Idiomatic Scheme for it might look like
(define (drop-tag x)
(if (string=? "tag" (car x))
(cdr x)
x))
Behavior
(drop-tag '("tag" "artist" "Phil" "Collins"))
("artist" "Phil" "Collins")
The symbolic and point-free version of this function can look like
(← drop-tag (? (∘ (⊥ string=? "tag") car) cdr id))
No Macro Required.
Does it look like Scheme anymore? No. Do I recommend you write like this? No. Will I continue language bikeshedding instead of finishing the parser? Yes—and that is what makes this approach truly Lispy.
In almost all cases, I only want to leave the final argument dangling. It would be interesting to see how exclude more arguments using this approach, but after thinking it over a bit, I'm not sure if it's possible. Please let me know if I'm mistaken; maybe I'm overlooking something obvious. I suppose for anything more complicated, you're better off using cut
and cute
from srfi-26. If you'd like even more compact Cojure-inspired syntax, I guess the following would be nice.
(define-syntax Λ (syntax-rules (% ▽ <>)
((_ ▽ () (ω ...)) (cut ω ...))
((_ ▽ (% β ...) (ω ...)) (Λ ▽ (β ...) (ω ... <>)))
((_ ▽ (α β ...) (ω ...)) (Λ ▽ (β ...) (ω ... α)))
((_ α ...) (Λ ▽ (α ...) ()))))
So that
(Λ cons % %) = (cut cons <> <>)
This is neither partial application nor a pure function, so I guess it's entirely off topic, but eh.