PurelyFunctional.tv Newsletter 379: get produces unexpected behavior, as expected

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

Issue 379 - May 25, 2020 ยท Archives ยท Subscribe

Clojure Tip ๐Ÿ’ก

get has unexpected behavior, as expected

Some people wonder about why get returns nil when the key is not found in the map. I actually like that behavior and dislike the behavior in other languages when it is an error to ask for something that doesn't exist. Errors when the key doesn't exist feel a bit like going into a restaurant and ordering spaghetti and getting kicked out because they don't have it. A simple nil would have sufficed.

Do you prefer:

(get some-map :key) ;=> nil

OR

(get some-map :key)
KeyNotFoundException on line 3212312

Obviously, Rich Hickey prefers the first one.

However, if you think returning nil when the key is not found is bad, try this one at the REPL:

(get :key some-map)

For those of you far from the REPL, the answer is nil.

(get :key some-map) ;=> nil

Ugh. That one seems really bad. You get the same answer with the arguments reversed. This does not seem like desirable behavior. In fact, it seems actively hostile.

If you look at the code for get, you can see this behavior implemented. It's in the Java method getFrom().

static Object getFrom(Object coll, Object key){
    if(coll == null)
        return null;
    else if(coll instanceof Map) {
        Map m = (Map) coll;
        return m.get(key);
    }
    else if(coll instanceof IPersistentSet) {
        IPersistentSet set = (IPersistentSet) coll;
        return set.get(key);
    }
    else if(key instanceof Number && (coll instanceof String ||
coll.getClass().isArray())) {
        int n = ((Number) key).intValue();
        if(n >= 0 && n < count(coll))
            return nth(coll, n);
        return null;
    }
    else if(coll instanceof ITransientSet) {
        ITransientSet set = (ITransientSet) coll;
        return set.get(key);
    }

 return null;
}

Notice the last line: a final return null if none of the types match. Java requires some return statement (or a thrown exception) and Rich chose to return null.

Clojure's functions are rife with this pattern: check a bunch of types, then return null if none match.

I've written about this more here. The biggest problem is that it violates most people's default assumptions about how types are checked in dynamic languages. It appears that types are not checked in Clojure, and that that policy was implemented systematically. Most people assume types will be checked in some way.

Clojure's behavior is very confusing to beginner and expert alike. The biggest benefit to this approach is that some code is faster. But many functions could throw exceptions with no performance hit. The only positive thing I can say is that you eventually get used to it. And empirically, this is true. Many companies are very productive with Clojure.

So, the bottom line is: be careful. REPL-driven development really helps uncover the problems sooner. Write code in small steps and test it frequently.

PurelyFunctional.tv Update ๐Ÿ’ฅ

Just before the quarantine, I enacted plans to discontinue new memberships to PurelyFunctional.tv. However, the timing was terrible! I planned to make individual course sales much more interesting, but I have not gotten to them yet.

I've reopened memberships to anyone who wants them. I'll discontinue them again later, with the same policy of allowing existing memberships to continue.

If you would like a membership, check out the Register page. Memberships give you access to 100 hours of video courses on Clojure, ClojureScript, and tooling.

Book update ๐Ÿ“–

Chapter 8 was just released as part of the Manning Early Access Program. Chapter 8 is all about Stratified Design, where we learn to organize our code into layers. This chapter took me a long time. It was hard for me to boil down this design skill.

You can buy the book and use the coupon code TSSIMPLICITY for 50% off.

Chapter 11 first draft is done. It's still rough, but it's good. It clocked in at 52 pages, which is kind of long. I hope I don't have to split it. I'm letting that chapter sit for a bit before I clean it up. Now I'm onto Chapter 12, all about update, nested update (with recursion!), and returning functions from functions. Will those three topics fit into one chapter? There's only one way to find out.

Quarantine update ๐Ÿ˜ท

I know a lot of people are going through tougher times than I am. If you, for any reason, can't afford my courses, and you think the courses will help you, please hit reply and I will set you up. It's a small gesture I can make, but it might help.

I don't want to shame you or anybody that we should be using this time to work on our skills. The number one priority is your health and safety. I know I haven't been able to work very much, let alone learn some new skill. But if learning Clojure is important to you, and you can't afford it, just hit reply and I'll set you up. Keeping busy can keep us sane.

Stay healthy. Wash your hands. Stay at home. Wear a mask. Take care of loved ones.

Clojure Challenge ๐Ÿค”

Last week's challenge

The challenge in Issue 378 was to classify the symmetry of patterns. You can see the submissions here.

Many people expressed surprise that this could be an Expert level in JavaScript. One solution was 8 lines long in Clojure. If you're really curious: write it out yourself!

You can leave comments on these submissions in the gist itself. Please leave comments! There are lots of great discussions in there. You can also hit the Subscribe button to keep abreast of the comments. We're all here to learn.

And I must say that I am so happy with the discussions happening in the gist comments. People are getting fast feedback from each other and trying out multiple implementations. Check it out.

This week's challenge

Word segmentation

One of the issues with domain names is that spaces aren't allowed. So we get domain names like this:

  • penisland.com (Pen Island)
  • expertsexchange.com (Experts Exchange)

Now we also have the problem with #hashtags on social media platforms.

We want to be able to take a string without spaces and insert the spaces so that the words are separated and our gradeschool teacher can be happy again.

Your task is to write a function that takes a string without spaces and a dictionary of known words and returns all possible ways it could be segmented (i.e., insert spaces) into those words. If it can't be segmented, it should return an empty sequence.

(segmentations "hellothere" ["hello" "there"]) ;=> ("hello
there")
(segmentations "fdsfsfdsjkljf" ["the" "he" "she" "it"...]) ;=> ()

Bonus: use a dictionary file and some text from somewhere and do a real test.

Super bonus: make it lazy.

Thanks to this site for the challenge idea where it is considered Expert level in JavaScript.

You can also find these same instructions here. I might update them to correct errors and clarify the descriptions. That's also where submissions will be posted. And there's a great discussion!

As usual, please reply to this email and let me know what you tried. I'll collect them up and share them in the next issue. If you don't want me to share your submission, let me know.

Rock on!
Eric Normand