I used to think of as-> as something I’d reach for when I needed to thread something into a middle position, but I just realized that it could just be used to add names to intermediate results, a bit like let, except I feel it might even be more readable.
I’ve never used as-> but the way you show it there is nice; it provides the readability-benefits I sometimes want from static typing. I’ll have to try it
It’s also worth pointing out how this relates to the discussion to comp or not to comp: when the readability benefits would be significant, this would be a great time “not to comp” and so minimize the “black box” effect.
If the chain of functions has only a single arg, I use -> If it is anything else, I always prefer to be explicit about how things are threaded, using it-> from the Tupelo library: https://github.com/cloojure/tupelo#literate-threading-macro
(it-> 1
(inc it) ; thread-first or thread-last
(+ it 3) ; thread-first
(/ 10 it) ; thread-last
(str "We need to order " it " items." ) ; middle of 3 arguments
;=> "We need to order 2 items." )
Hum, that’s an interesting one as well, never saw it before.
It reminds me a bit of:
(-> (process payload)
(get-cart ,)
(checkout ,))
But instead of just documenting the position, it also names it.
I think I don’t find it as readable though, because mentally I already read get-cart, and so I lost the context of the previous form, so now when I see customer I have to mentally backtrack like oh okay process returned a customer.
(as-> 1 it
(inc it) ; thread-first or thread-last
(+ it 3) ; thread-first
(/ 10 it) ; thread-last
(str "We need to order " it " items." ) ; middle of 3 arguments
;=> "We need to order 2 items." )
Although I would still put it in a regular -> pipeline and avoid the “weird” value/binding order of as->:
(-> 1
(as-> it
(inc it) ; thread-first or thread-last
(+ it 3) ; thread-first
(/ 10 it) ; thread-last
(str "We need to order " it " items." ) ; middle of 3 arguments
;=> "We need to order 2 items." ))
That second example doesn’t work for me. Think you meant:
(-> 1
(as-> it
(inc it) ; thread-first or thread-last
(+ it 3) ; thread-first
(/ 10 it) ; thread-last
(str "We need to order " it " items." ))) ; middle of 3 arguments
;=> "We need to order 2 items." )
It has always seemed to me that the placeholder symbol should be the first argument, not the second. I kept getting it backwards until I finally remembered to reverse them from what they “should” be.
There is often no good name for the placeholder symbol. The English pronoun it is perfect name for something that changes with each successive function.
The Groovy programming language already uses it as a default variable name for single-arg functions, and I was familiar with this ideom. So, I decided to re-use a good idea that already existed.
Well, as-> is designed to be used inside-> so you normally would never provide both the value and the symbol, and it is a perfectly good symbol to use:
(-> value
(as-> it
(... it ...)))
Seems weird to me, to bring in a library, to avoid using something that is already in core (especially after so many people clamored for so many years for a threading macro like this).
To be fair, it triggers my OCD that you’ve got this extra weird indentation and added parenthesis nesting. That’s why I also have a -<> macro I use for myself. But, I also hate that, because bringing in a library just for that also bugs me.
So I kinda ended just using let instead for those cases.
But now I’m thinking I can use as-> selectively, so in your example I could do:
(-> 1
(inc)
(+ 3)
(as-> n (/ 10 n))
(as-> n (str "We need to order " n " items." )))
And ya, I think I like duplicating the as->, even though I know I could just do:
(-> 1
(inc)
(+ 3)
(as-> n (/ 10 n)
(str "We need to order " n " items." )))
Not too sure yet, I’ll see which I eventually prefer.