Fork me on GitHub
#beginners
<
2020-05-30
>
sylvain09:05:35

I'm writing some code where many of the functions in my namespace take the same first 2 parameter types as input so I end up with code that looks like this:

(defn some-function1 [state block]
  (let [local-func1 (partial func1 state block)
        local-func2 (partial func2 state block)
        local-func3 (partial func3 state block)

        param1 (local-func1 "data0")
        param2 (local-func3 {:some-key "data1"})
        param3 (local-func1 "data3")
        param4 (local-func2 "data4")]
    (-> state
        (func4 block param1 param2)
        (func5 block param3)
        (func5 block param4)
        (func6 "data5"))))

(defn some-function2 [state block value]
  (let [local-func2 (partial func2 state block)
        local-func4 (partial func4 state block)
        
        param1 (local-func2 "data6")]
    (-> state
        (func4 block value)
        (func5 block param1))))

(defn some-function3 [state block other-value]
  ;; ...
  )
I feel like this could be improved (e.g not having to manually bind the local functions at the beginning of the let would be nice) but I'm not sure how to go about it. Any suggestions ? Is this a case where using one of those monadic libraries would help ?

aisamu16:05:58

If you don't want bindings, (and since you only use the functions once) you could make a helper function that makes those closures for you

(defn some-function1 [state block]
  (let [with-context #(apply % state block %&)
        param1 (with-context func1 "data0")
        param2 (with-context func3 {:some-key "data1"})
        ...

rymndhng05:05:12

without context about what block is — it’d be pretty hard to say. Also, at some point in your application, there’s going to be a complex piece that takes all your little functions to do something useful. This seems like that function, and from experience, trying to reduce the lines of code or hide the binding could introduce difficulty debugging. Something else to keep in mind is that the fewer intermediate vars you introduce, the fewer good names you have to find. I think the code without the partial functions is fairly reasonable:

(defn some-function1 [state block]
  (let [param1 (func1 state block "data0")
        param2 (func3 state block {:some-key "data1"})
        param3 (func1 state block "data3")
        param4 (func2 state block "data4")]
    (-> state
        (func4 block param1 param2)
        (func5 block param3)
        (func5 block param4)
        (func6 "data5"))))

sylvain07:05:52

Thanks for your answers. I need to mull over this.

michaelb11:05:07

this is more of a general jvm thing, I guess, but I’m developing with Clojure so maybe someone here will have a suggestion… Using jpackage in OpenJDK 14 I’m packaging my app’s uberjar (built with lein) for Linux, macOS, and Windows. On Windows only, the installed app (i.e. installed with the installer created by jpackage) sometimes unexpectedly closes a few seconds after opening it. I have not seen that behavior on Windows when e.g. running the code from a REPL, so I’m really not sure if it’s something wrong with my code, or if it’s something buggy re: the artifacts created by jpackage. When the app unexpectedly closes, there’s no error dialog, so no clue yet what’s going on. On the assumption it’s something that can be caught and logged (e.g. written to C:\appcrash.log) how can I go about doing that in the most general way possible since it’s not clear where to put try/catch?

Lukas11:05:38

Hey guys, I wrote a simple web server jetty/ring. I have my server running in the repl (emacs cider) and I would like to reload my changes without closing my repl and starting it again. How can I do this?

Vishal Gautam11:05:12

There is a middleware called ring.middleware.reload. That should do the job

seancorfield16:05:33

Beware of reload/refresh workflows: they seem "easy" but they are not "simple" and you can get into difficulties with them.

seancorfield16:05:00

A much better workflow is to simply eval each change, as you make it, into the running image.

seancorfield16:05:43

It requires discipline tho'. You must get into the habit of eval'ing every single function change as you make it. I "evaluate top block" each time I change a function or add a new function. I also take care to use #'my-fn instead of plain my-fn where functions are passed as arguments (such as the handler function passed into Ring middleware or into the server start function). That extra indirection allows the process to see changes to functions after compilation.

👍 12
Lukas16:05:50

Thanks a lot, :thinking_face: I guess I lack some fundamentals here 🙈. I started my repl, required the namespace of my server and did start the server. Everytime i just evalualted my changes it didnt refresh the output. I have a simple handler that just prints some basic html and i tried to change the string -> evaluate and the change did not apply after reloading the page

Lukas16:05:09

@U04V70XH6 thanks again! Especially for the tip using this #'my-fn

seancorfield16:05:07

See https://clojure.org/guides/repl/enhancing_your_repl_workflow#writing-repl-friendly-programs (the whole REPL guide is pretty good but that section in particular is important).

❤️ 4
Lukas16:05:40

Hey, I'm using ring and the cookie middleware. I attach this map to the cookie :cookies {:session-id {:value "hash?"}} but when I get it back in the next request :session-id changed to a string e.g. {"session-id" {:value "hash?"}} which feels kinda unnatural to work with. Is there a good workaround for this issue (change it back to clojure key)?

lsenjov16:05:52

Write your own middleware, throw it further down?

lsenjov16:05:13

Middleware is pretty simple to write

(defn wrap-my-middleware
  [handler]
  (fn [request]
    (-> request do-before handler do-after)))

👍 4
Lukas16:05:28

thanks you

lsenjov16:05:37

Although it may be an idea just to work with it as a string

lsenjov16:05:51

Because other middleware libraries will also want to work with it as a string

✔️ 4
4
Kazuki Yokoyama23:05:41

Hi, people. Which one is considered to be more idiomatic?

(->> some-seq (map #(some-fn some-param %)) <further processing>)
or
(->> some-seq (map (partial some-fn some-param)) <further processing>)
Thank you!

andy.fingerhut23:05:54

I am not aware of anything that is more idiomatic about one than the other, personally. Both I find straightforward and readable. I personally prefer to write the first, myself, just because I haven't gotten in the habit of writing calls to partial

seancorfield23:05:50

Because map takes the sequence as its last parameter, you need ->> there not ->.

seancorfield23:05:07

Rich Hickey has said that he considers #( ,,, % ,,, ) to be more idiomatic than partial I believe.

Kazuki Yokoyama23:05:25

@U0CMVHBL2, when possible, I find partial a little more readable (even when compared to fn), though anonymous functions are more flexible.

andy.fingerhut23:05:25

I think the reason I usually reach for #( ,,, ) before other things is that it handles any number of parameters, with those parameters used within the body in arbitrary places, i.e. it is more general purpose than partial. Again, that is my personal preference, and I woudn't try to stop anyone from using partial when it is appropriate.

Kazuki Yokoyama23:05:53

@U04V70XH6 ops, my bad! Already edited and fixed, thanks! Do you have the source for this at hand? It would be great see more examples.

andy.fingerhut23:05:12

i.e. while writing code, with #( ...) I do not need to think for even a moment whether partial is appropriate in that particular case.

andy.fingerhut23:05:15

Another minor point to consider: (fn some-name ,,)` can be nicer when looking at stack traces, since some-name will appear there, and IIRC both #( ,,,) and partial will have some less useful auto-generated name in the stack trace.

Kazuki Yokoyama23:05:22

Yes, #(...) and fn are more flexible and general. I think I'll stick with anonymous functions in cases like that. Thank you!

Kazuki Yokoyama23:05:01

Good point in favor of fn.

seancorfield23:05:16

A quick test with partial and #(...) shows that the enclosing function information is lost in a stacktrace with partial but retained with the anonymous function... I'll post an example:

seancorfield23:05:50

user=> (defn foo [& args] (try (throw (ex-info "" {})) (catch Throwable t (take 6 (:trace (Throwable->map t))))))
#'user/foo
user=> (defn bar1 [] (map (partial foo) [1 2 3]))
#'user/bar1
user=> (defn bar2 [] (map #(foo %) [1 2 3]))
#'user/bar2
user=> (first (bar1))
([user$foo invokeStatic "NO_SOURCE_FILE" 12] [user$foo doInvoke "NO_SOURCE_FILE" 12] [clojure.lang.RestFn invoke "RestFn.java" 408] [clojure.core$map$fn__5851 invoke "core.clj" 2753] [clojure.lang.LazySeq sval "LazySeq.java" 42] [clojure.lang.LazySeq seq "LazySeq.java" 51])
user=> (first (bar2))
([user$foo invokeStatic "NO_SOURCE_FILE" 12] [user$foo doInvoke "NO_SOURCE_FILE" 12] [clojure.lang.RestFn invoke "RestFn.java" 408] [user$bar2$fn__21344 invoke "NO_SOURCE_FILE" 14] [clojure.core$map$fn__5851 invoke "core.clj" 2753] [clojure.lang.LazySeq sval "LazySeq.java" 42])
user=>

seancorfield23:05:48

bar1 is not visible in the stacktrace with partial, but bar2 is visible in the stacktrace with #(..)

Kazuki Yokoyama00:05:27

Yes, anonymous functions seem to have this advantage! As expected, this also holds for fn .

slipset16:05:51

I’m partial to partial as it indicates that the pnly thing that’s happening is that I’m binding parameters. #(...) and (fn ...) forces me to read the fn to figure out what it does.

slipset16:05:10

Also, I’d argue that if your #(...) or (fn ...) has more than a couple of forms in it, you should extract it and give it a name. I’ve seen too many horror-story anon fns spanning hundreds of lines.

seancorfield18:05:26

I'll definitely agree with that last part -- I don't like %1, %2, etc, so I tend to only use #(,,%,,) for a single argument function and only for small expressions.