10 ms·
Functional Threading "Macros"
- kazinator 1y agoThe main problem that threading macros solve is the lack of implicit partial application. If you farm that problem to partial-applicative combinators, you can do it: This is the TXR Lisp interactive listener of TXR 302. Quit with :quit or Ctrl-D on an empty line. Ctrl-X ? for cheatsheet. 1> (defun bind1 (fn a1) (lambda (a2) [fn a1 a2])) bind1 2> (defun bind2 (fn a2) (lambda (a1) [fn a1 a2])) bind2 We define bind1 and bind2 for binding the left or right argument of a binary function producing a unary function, then: 3> [chain [bind1 * 2] succ] #<intrinsic fun: 0 param + variadic> 4> [*3 10] 21 The only wart is you have that explicit bind1. You can write functions that notice they have fewer arguments than ostensibly required and partially apply themselves. Obviously the standard * can't do that because it's usefully variadic already. Anyway, in the implementation of TXR, I've done this kind of thing in C, just with function calls: no macros. E.g. in eval.c, certain functions are prepared that the quasiquote expander uses: static val consp_f, second_f, list_form_p_f, quote_form_p_f; static val xform_listed_quote_f; static void qquote_init(void) { val eq_to_list_f = pa_12_1(eq_f, list_s); val eq_to_quote_f = pa_12_1(eq_f, quote_s); val cons_f = func_n2(cons); protect(&consp_f, &second_f, &list_form_p_f, "e_form_p_f, &xform_listed_quote_f, convert(val *, 0)); eq_to_list_f = pa_12_1(eq_f, list_s); consp_f = func_n1(consp); second_f = func_n1(second); list_form_p_f = andf(consp_f, chain(car_f, eq_to_list_f, nao), nao); quote_form_p_f = andf(consp_f, chain(cdr_f, consp_f, nao), chain(cdr_f, cdr_f, null_f, nao), chain(car_f, eq_to_quote_f, nao), nao); xform_listed_quote_f = iffi(andf(consp_f, chain(car_f, eq_to_list_f, nao), chain(cdr_f, consp_f, nao), chain(cdr_f, cdr_f, null_f, nao), chain(cdr_f, car_f, consp_f, nao), chain(cdr_f, car_f, car_f, eq_to_quote_f, nao), nao), chain(cdr_f, car_f, cdr_f, pa_12_1(cons_f, nil), pa_12_2(cons_f, quote_s), nao), nil); } "nao" means "not an object": it's a sentinel value used internally in the runt-time for various purposes, the most common of them being the termination of variadic argument lists. andf is an and combinator: it returns a function which passes its argument(s) to each function in turn; if anything returns nil, it stops calling functions and returns nil. Otherwise it returns the value of the last function. The pa_this_that functions are partial applicator combinators, generalizations of bind1 and bind2. E.g. pa_12_1 means take a fucntion with arguments 1 2, returning a function which just takes 1 (so 2 is bound: this is like bind2). A bunch of these exist, and more coud be added if needed: pa_1234_1, pa_1234_34 pa_123_1, pa_123_2, pa_123_3, pa_12_1 and pa_12_2.
- adityaathalye 1y agoIf the language allows passing lists of functions, then `comp` can be implemented by hand: https://clojuredocs.org/clojure.core/comp https://clojuredocs.org/clojure.core/comp And re-implementing `comp` by hand can teach us more than we bargained for (all the way to compiler technology)... I blogged about it here: https://www.evalapply.org/posts/lessons-from-reimplementing-clojure-comp-function/ https://www.evalapply.org/posts/lessons-from-reimplementing-... ;; Clojure source code for `comp` (since Clojure v1.0) (defn comp "Takes a set of functions and returns a fn that is the composition of those fns. The returned fn takes a variable number of args, applies the rightmost of fns to the args, the next fn (right-to-left) to the result, etc." {:added "1.0" :static true} ([] identity) ([f] f) ([f g] (fn ([] (f (g))) ([x] (f (g x))) ([x y] (f (g x y))) ([x y z] (f (g x y z))) ([x y z & args] (f (apply g x y z args))))) ([f g & fs] (reduce1 comp (list* f g fs))))
- adityaathalye 1y agofogus has a couple of nice little posts on the threading macro itself... https://blog.fogus.me/2013/09/04/a-ha-ha-ha-aah.html https://blog.fogus.me/2013/09/04/a-ha-ha-ha-aah.html https://blog.fogus.me/2009/09/04/understanding-the-clojure-macro.html https://blog.fogus.me/2009/09/04/understanding-the-clojure-m...
- adityaathalye 1y agoOh and, the arrow-kt library bolts this onto kotlin. Utilities for functions: https://arrow-kt.io/learn/collections-functions/utils/ https://arrow-kt.io/learn/collections-functions/utils/
- nesarkvechnep 1y agoStarts with saying they like Common Lisp but use Clojure daily, then proceeds with some language called "lamber". What?
- shawn_w 1y agoFrom the post: >The language I’m using in this post is Lamber, my Lambda Calculus compiling language. It features a minimalist syntax with only functions, values, if-s, and operators like Wisp’s colon nesting operator and terminating period (similar to Lua’s end.)