2
votes

Let's say I have the following function to get numeric values from a byte buffer:

(defn get-from-bytebuffer
  ([^ByteBuffer buffer width endianness]
    (let [buffer-endianness (.order buffer)]
      (.order buffer endianness)
      (cond
        (= width 1) (.get buffer)
        (= width 2) (.getShort buffer)
        (= width 4) (.getInt buffer)
        (= width 8) (.getLong buffer))
      (.order buffer buffer-endianness))))

The user can specify the endianness of the number being read. To keep side effects to a minimum, the function first gets the current byte ordering of the buffer, sets it to the one indicated by the user, then restores the old endianness. The problem with this is that the value of the last expression in the body of the let is the value of the let expression, but I need the value of the cond. More generally, I have some prologue/epilogue code around an expression but I want the result of the expression to be returned (be the value of the enclosing expression.)

The easy workaround that I came up with simply binds the value of the cond in another let expression, then has that as the last expression like so:

(defn get-from-bytebuffer-fix
  ([^ByteBuffer buffer width endianness]
    (let [buffer-endianness (.order buffer)]
      (.order buffer endianness)
      (let [result (cond
                     (= width 1) (.get buffer)
                     (= width 2) (.getShort buffer)
                     (= width 4) (.getInt buffer)
                     (= width 8) (.getLong buffer))]
      (.order buffer buffer-endianness)
      result))))

But this feels kludgy. Does Clojure have an idiomatic/"proper" way of surrounding an expression with some prologue/epilogue code and then returning the value of that expression?

2
See also this and this previous questions.Omri Bernstein

2 Answers

3
votes

Maybe one of the alternatives below would be acceptable.

(let [buffer-endianness (.order buffer)
      _ (.order buffer endianness)
      result (cond
               (= width 1) (.get buffer)
               (= width 2) (.getShort buffer)
               (= width 4) (.getInt buffer)
               (= width 8) (.getLong buffer))
      _ (.order buffer buffer-endianness)]
  result)

(let [buffer-endianness (.order buffer)]
  (try 
    (.order buffer endianness)
    (cond
      (= width 1) (.get buffer)
      (= width 2) (.getShort buffer)
      (= width 4) (.getInt buffer)
      (= width 8) (.getLong buffer))
    (finally (.order buffer buffer-endianness))))

(defn return-nth [n & exprs]
  (nth exprs n))

(return-nth 1
  'exp-0
  'exp-1
  'exp-2)

(nth ['exp-0 'exp-1 'exp-2] 1)

Also note that

(cond
  (= width 1) (.get buffer)
  (= width 2) (.getShort buffer)
  (= width 4) (.getInt buffer)
  (= width 8) (.getLong buffer))

can be written

(case width
  1 (.get buffer)
  2 (.getShort buffer)
  4 (.getInt buffer)
  8 (.getLong buffer))
2
votes

You could write it as:

(defn get-from-bytebuffer
  ([^ByteBuffer buffer width endianness]
    (let [buffer-endianness (.order buffer)
          _ (.order buffer endianness)
          result (cond
                   (= width 1) (.get buffer)
                   (= width 2) (.getShort buffer)
                   (= width 4) (.getInt buffer)
                   (= width 8) (.getLong buffer))]
      (.order buffer buffer-endianness)
      result)))

Note the _ in the let form. It simply ignores the return value allowing you to do pretty much anything in that position.

I wouldn't say it's idiomatic and in a way it's 'weird' in that it has side effects in the let binding but I've used this style before in similar scenarios and so far I'm happy with it.

Otherwise I'd have gone the double let approach you've showed.

Hope this helps.