PurelyFunctional.tv Newsletter 328: Tip: don't use def inside a defn
Issue 328 - May 27, 2019 · Archives · Subscribe
Clojure Tip 💡
don't use def
inside a defn
Unless you really have a great reason, def
s inside of a defn
are
generally incorrect. If you really do have a good reason, there should
be a comment saying why you're doing it. You probably don't do this,
dear reader, but I've been looking at beginner Clojure code and I am
surprised by how many times it happens.
Why not use def
inside a defn
?
Good question. Really, def
defines a global variable. It's a variable
shared by all invocations of that function. It's not thread safe. Two
threads calling that function at the same time will collide with each
other. Besides, it's not how def
was meant to be used.
So what should you use instead?
That's an awesome question. It really depends on what you're trying to
do. If you're just trying to save some data somewhere so that you can
access it later in the function, you probably want a
let
. let
creates local
variables that are not shared by different invocations. The values are
cleaned up as soon as the let
expression ends.
If you want to save data to share with other functions, Clojure also provides several ways of sharing mutable data that are thread safe. I recommend starting with atoms. Atoms provide a simple way to share a single value. That value can be a collection, so you can share a whole lot inside of one atom.
If you're implementing an imperative algorithm, which would normally mutate data in a variable, you're up for a challenge. It's not so straightforward to implement an arbitrary algorithm in a functional way. You can give it a try, using recursion. But often, you don't need recursion! Many algorithms can be implemented using the functions in the Clojure standard library. Start with map, filter, and reduce.
Clojure Tool 🔨
You probably already know about this tool, but if you don't, you need to. ClojureDocs is a community-driven documentation site for Clojure's standard library and many other popular libraries. Some of its nice features include:
- Searching for functions across namespaces and libraries
- Includes the standard documentation
- Includes "see also" references to related functions as links
- Allows members to add examples, clearly showing how to use a function and often some of the problems you might encounter
- Links to source code
I really like it for finding and understanding new functions. Just having an example is really helpful.
Clojure Puzzle 🤔
Last week's puzzle
The puzzle in Issue 327 was to search for the largest Lynch-Bell number.
You can see the submissions here.
Some people went all out trying to optimize it. There's some reasoning you can do to cut down the search space. Check out the interesting implementations.
There's a fairly fast, straightforward implementation that only takes
half a second on my machine that I implemented based on Mark Champine's
answer. His was very clear. The only trick is that instead of calling
max
to find the largest one (as you normally would), you just start
from the high end, working your way down, and return the first one you
find. That one has to be the largest.
(defn largestlb
"Find the largest Lynch-Bell number up to n"
[n]
(first (filter lbtest (range n 1 -1))))
This week's puzzle
Redo: largest concatenation
Folks, two weeks ago, my puzzle was to write a function that returned the largest concatenation of integers. I got lots of answers, which I shared last week. But Val Waeselynck let me in on something: only his worked for this very simple case:
(maxcat [90 9])
The other solutions (including the one I called "exemplary" in the
newsletter), return 909
, while 990
is clearly bigger.
I have to admit something: I didn't test them thoroughly, either. I always expected you, the puzzle player, to test them yourselves. Wouldn't that make a better exercise? But I think it's too much to ask. Who has the time?
So this week, I'm resurfacing the same largest concatenation problem as before. But this time, I've created a testbed. It's a small project with tests. You fill in the stub function with your implementation and test it. Feel free to read the tests for insights.
Here's the problem again:
Largest integer from concatenation
If I have a list of integers, I can concatenate their base-10 strings to get one large integer. However, if I reorder them, then concatenate them, I could get a larger integer. The task here is to find the largest integer you can create by concatenating a given set of integers.
The testbed can be found here.
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 Newsletter Maintenance 🔧
Folks, I migrated the email service I use for this newsletter. You're now reading this on the new service.
Email migrations can be tricky. I would really appreciate it if you report any issues with deliverability. That includes not receiving the email, the email showing up in spam, and any warning messages you get in your email client. I want to make sure you can get the emails you want.
The most obvious benefit to you of the new system is that I am no longer tracking links you click on. Unlike my old system, my new system can turn off link tracking. You actually know where the link will send you before you click. And nobody is tracking the clicks.
I also believe the new system has better deliverability, meaning you're more likely to get it. I am still tracking email opens, because I would like to make sure that people are getting the email. Open tracking is the best way to assess the health of your email delivery system.