PurelyFunctional.tv Newsletter 319: Tip: Shebang scripting with the Clojure CLI
Issue 319 - March 25, 2019 · Archives · Subscribe
Clojure Tip 💡
You can do shebang scripting with the Clojure CLI
Well, it's not exactly speedy, but it is practical.
I have some longish running programs that I run occasionally. They are
written in Clojure. I could make a -main
function and generate an
uberjar. But I want something more lightweight.
I also didn't want to have to create a new directory and deps.edn just for that one project. My ideal would be a single file.
Well, I achieved it.
What you'll see at the other end of this link is a bit of shell and Clojure sorcery. It's a file that runs with shell scripting, but can also run as Clojure. That means you can set your deps and other JVM options right in the file at the top, and write Clojure code down at the bottom.
I shared an early version of this on Twitter and got some really good advice from Gary Fredericks, Francis Avila, Devin Walters, and Tim Pote.
Conference alert 🚨
Clojure/north is happening soon. Get your tickets now. The speakers look awesome. We need to support more conferences in North America.
Brain skill 😎
Get plenty of exercise. Besides being good for your general health, it turns out that physical activity helps wire new neurons. If you're going to take a break, consider going for a walk, a run, or doing some calisthenics.
Clojure Puzzle 🤔
Last week's puzzle
The puzzle in Issue 318 was to write a function to generate all combinations of a collection, basically implementing the 5 Choose 3 operation.
There were only two submissions this week. Too hard? The two I got were nice, and I added my own to make it feel a little more complete. See them all here.
But I want to call out each of the submissions, because they are each interesting.
Mark Champine
(defn combinations [n items]
(cond
(= n 0) #((#{))}
(empty? items) #{}
:else
(concat (map
#(clojure.set/union (hash-set (first items)) %)
(combinations (dec n) (rest items)))
(combinations n (rest items)))))
Mark chose to stick with the puzzle closely and use sets for everything.
It's a classic and clear recursive implementation, using
clojure.set/union
.
Steve Miner
(defn combinations [coll n]
(let [choose-indices (fn [cnt choose]
;; {:pre [(pos-int? choose) (<= choose cnt)]}
(loop [i (dec choose) res (map vector (range cnt))]
(if (zero? i)
res
(recur (dec i)
(mapcat (fn [prev] (map #(conj prev %)
(range (inc (peek
prev)) cnt)))
res)))))]
(if-not (pos-int? n)
(list ())
(let [v (vec coll)
cnt (count v)]
(when (<= n cnt)
(map #(into #{} (map v) %) (choose-indices cnt n)))))))
Steve seemed to go more for speed in his implementation. He uses a
loop/recur
, with an iterative method instead of recursive. He probably
wins a huge performance gain from that. His implementation operates on
the indices of the collection instead of on the values themselves, and
then converts them back to the values in the end.
Eric Normand
(defn combinations' [r coll n]
(cond
(> n (count coll))
nil
(zero? n)
nil
(= 1 n)
(map list coll)
:else
(let [x (first coll)
xs (rest coll)]
(lazy-cat
;; combinations with first element
(map #(conj % x) (r r xs (dec n)))
;; combinations without first element
(r r xs n)))))
(defn combinations [coll n]
(let [f (memoize combinations')]
(f f coll n)))
I had fun doing my implementation. I chose a straightforward recursive implementation, similar to Mark. I chose to stick with lists for the data structure instead of sets. I thought it would be nice to keep the order and keep duplicates. You can always turn them into sets later if you need them. It also gave a pretty good speedup.
I also memoize the helper function, and use a technique I picked up somewhere for using a local memoization. You basically pass the memoized function to itself, instead of doing an explicit recursive call.
Thanks for participating!
This week's puzzle
list of digits
Write a function that takes a number and returns a sequence of digits. Here's an example:
(digits 3452)
;=> [3 4 5 2]
For extra credit, let digit take another parameter, which is the base of the number in which to represent digits. The default will be 10, but it should work for any number >= 2.
(digits 3452 2) ;; 2 means binary
;=> [1 1 0 1 0 1 1 1 1 1 0 0]
Bonus points for clarity, interest, and efficiency.
As usual, please send me your implementations. I'll share them all in next week's issue. If you send me one, but you don't want me to share it publicly, please let me know.
Rock on! Eric Normand
PS ✍️
My new course called Repl-Driven Development in Clojure has 2 new lessons.
The first one is about Clojure's execution semantics. I've watched a lot of talks on Repl-Driven Development (RDD) and read a lot of blog posts as part of my research for this course. There's a lot of great stuff out there. However, one thing was missing: an explanation of how Clojure executes code.
You can't effectively do RDD without knowing how Clojure works. What happens when you load a file? What happens if you recompile the namespace declaration. What do Vars do? All of these questions are answered in [this lesson](https://ericnormand.podia .com/beginning-clojure).
The second lesson is all about compiling and executing code from your editor. These are the fundamental commands that you will need to do Repl-Driven Development.
And guess what? The top 5 editors all implement them. And you only need to learn 3 keystrokes to do it.
The course is available as of right now as part of an early access program (read serious discount). If you buy now, you'll receive updates to the course as new lessons come out. There is already 2.33 hours of video, and many more coming. If I had to guess, I'd say 6-8 hours total when I'm done. But I can't be sure. That's what the discount is for. As the course is fleshed out, the price will go up.
Of course, members of PurelyFunctional.tv will get the course as part of their membership for no extra cost. Just another benefit of being a member.
Check out the course. The first lesson is free.