Vanilla curry

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.

Symbols

(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.

Partial application

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 止?

Example

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.

Back to macros

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.