PurelyFunctional.tv Newsletter 328: Tip: don't use def inside a defn

Sign up for weekly Clojure tips, software design, and a Clojure coding challenge.

Issue 328 - May 27, 2019 · Archives · Subscribe

Clojure Tip 💡

don't use def inside a defn

Unless you really have a great reason, defs 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 🔨

ClojureDocs

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.