Fork me on GitHub
#beginners
<
2024-01-31
>
James Amberger03:01:14

Suppose I am running my code like this: clojure -M -m my.core , is there anything I can put in my deps.edn to make that even shorter?

Bob B04:01:46

I believe you could throw :main-opts into an alias and make it a vector of "-m" and "my-core"

James Amberger04:01:55

and then you’re left on the cli with what, clojure -M:thealias ?

James Amberger04:01:30

hmm. doesn’t it seem like there ought to be a way to specify this in the deps.edn? So for example you come back to the project or you share the project or whatever, you wouldn’t have to consult the deps.edn or documentation or a helper script in order to launch. I know there are lots of options here (build a jar, etc.) but my intuition is that should be one of them.

Bob B04:01:47

I think there's some ambiguity/preference around "launch"... clojure -M will start up a REPL for the project, so that's a "launch". I think there's a convention from deps-new, if memory serves, of creating a "run-m" alias, so that could be a convention that wouldn't require consulting more than knowledge of the convention. My guess would be that questions along this line may have been discussed in the past, so it might not hurt to poke around on http://ask.clojure.org and see if a similar discussion (or different approaches to a similar idea) have been unearthed. <https://ask.clojure.org/index.php/11355/could-clojure-main-read-the-main-namespace-from-manifest-mf?show=11355#q11355> feels like it might be going for a similar/related goal (albeit with a different motivation)

James Amberger04:01:38

Yes I see what you mean. In practice though how many people are calling clojure -M by itself just for the naked repl? If e.g. I actually want to interact with the repl via tty I use M:repl/rebel and if (more often) via the editor, I invoke via an alias for the nrepl that my editor needs.

Bob B04:01:05

I jack-in without specific aliases quite often. Calva adds stuff to the command line for its deps, but tbh editor-specific configuration is something I'm less fond of putting into my deps.edn than, say, an alias for starting a main function that's part of the project no matter who is using it in what editor.

James Amberger04:01:17

oh, so your editor does the invoking for you? (I use vim-fireplace and unless I missed something it’s on me to get the nrepl running [which is fine by me]). Anyway, I agree that a “run-m” convention is 99% of the way there

Bob B05:01:27

ah, so you can have a user-level alias... my mind was stuck in the project deps.edn

James Amberger05:01:00

Thank you for the chat @U013JFLRFS8

👍 1
practicalli-johnny06:01:21

In a project deps.edn file I include an alias to run the project using its main namespace which contains a -main function (this is what clojure.main looks for by default) Using Clojure CLI to run Clojure code, then clojure.exec can be used to run a specific function from the project. So I will have an aliases section like:

:aliases
 {;; Clojure.main execution of application
  :run/service
  {:main-opts ["-m" "practicalli.gameboard.service"]}

  ;; Clojure.exec execution of specified function
  :run/greet
  {:exec-fn   practicalli.gameboard.service/greet
   :exec-args {:name "Clojure"}}

;; test runner alias
;; tools build alias
}
The https://practical.li/clojure/clojure-cli/projects/templates/practicalli/ provide these aliases by default. I also include a Makefile in my projects to provide a consistent CLI interface for all projects, e.g. make repl , make tests (just and babashka are other tools that can be used as a layer on top)

silian03:01:45

Clojure challenge! Write a function that returns ["7" "9" "11"] when the following form is evaluated:

(pipe str inc (partial * 2) [3 4 5])
Edit: human math error

clojure-spin 1
silian03:01:07

chatGPT is giving some very interesting answers!

Bob B03:01:38

it's not immediately clear to me how this code gets from 3 4 5 to 7 8 9... doubling each then requires inc'ing, identity'ing, and dec'ing to get to the end numbers... is that definitely the desired output?

silian03:01:43

And converted to string!

James Amberger03:01:03

wait is the idea to (defn pipe …) such that that form evals to [7 8 9] ?

silian03:01:54

Not [7 8 9] but ["7" "8" "9"]!

silian03:01:22

Each item in the collection should be curried through any number of functions to the left of the collection.

Bob B03:01:49

but 5 * 2 + 1 (implied by inc) isn't 9...

silian03:01:18

The last item passed to pipe should always be the collection (or even single item).

silian04:01:01

pipe has to be a fn where:

(let [coll (last args)
      fns (butlast args) ;... ])

Bob B04:01:31

if the correct output is ["7" "9" "11"], then it's mapping (apply comp fns) over coll

✍️ 1
silian04:01:54

And then each item in the coll gets “piped” though the fns from right-to-left (hence idiosyncratically non-Clojure named pipe operator).

silian04:01:10

Bob, does map have to be involved? I was trying to do it with reduce somehow maybe.

silian04:01:54

Probably map is the simplest implementation.

James Amberger04:01:05

I mean you can write map in terms of reduce

Bob B04:01:06

if we ignore the laziness aspect of map, map can be written in terms of reduce, so you could reduce (to re-implement map)

silian04:01:43

Very nice. Thanks both.

Bob B04:01:19

To be clear, the pipe function is basically just comp but taking exactly one extra parameter (assuming the let with last), and since the last thing is assumed to be a collection, map (or mapv for an eager vector) is then used to apply the function. Using comp would allow for extra arguments by delineating the operation(s) from the subject.

Bob B04:01:21

(def frobnicate (comp str inc (partial * 2)))
(frobnicate 17)
=> "35"
(map frobnicate (range 2 8 2))
=> ("5" "9" "13")

silian04:01:50

frobnicate 😂 Very nice, but I still want to work out how I can define my own fn so users can pass arbitrary fns to pipe.

silian04:01:56

I don't have any particular use in mind, just think it would be a good exercise and was surprised the concept didn't already exist in clojure.core (but probably for good reason!)

Bob B04:01:56

(defn pipe [& fs-and-coll]
  (let [fs (butlast fs-and-coll)
        coll (last fs-and-coll)]
    (mapv (apply comp fs) coll)))
(pipe str inc (partial * 2) [3 4 5])
=> ["7" "9" "11"]
that'll do the trick (again, assuming the math was just wrong), but if the right-most function takes multiple params or if the final argument isn't a collection, then it won't work (which I'd imagine is why the concepts are separated in clojure.core)

clj 1
silian04:01:16

My math was wrong! :woman-facepalming::skin-tone-2:

adi05:02:30

There are many ways to slice this cake. For example, using https://blog.fogus.me/2010/09/28/thrush-in-clojure-redux/.

(defn thrush [& args]
  (reduce #(%2 %1) args))

(thrush 5 #(+ % 4) -)
;=> -9 
Or, since functions are first-class values, one can stick them into collections, and https://github.com/adityaathalye/clojure-by-example/blob/master/src/clojure_by_example/ex03_data_and_functions.clj#L216.
(def minimal-good-conditions
  "A collection of functions that tell us about the
  good-ness of planetary conditions."
  [co2-tolerable?
   gravity-tolerable?
   surface-temp-tolerable?])


(def fatal-conditions
  "A collection of functions that tell us about the
  fatality of planetary conditions."
  [(complement atmosphere-present?)
   air-too-poisonous?])


(defn conditions-met
  "Return only those condition fns that a planet meets.
  An empty collection means no conditions were met."
  [condition-fns planet]
  (filter (fn [condition-fn]
            (condition-fn planet))
          condition-fns))


(defn planet-meets-no-condition?
  [conditions planet]
  (empty? (conditions-met conditions planet)))


(def planet-meets-any-one-condition?
  (complement planet-meets-no-condition?))


(defn planet-meets-all-conditions?
  [conditions planet]
  (= (count conditions)
     (count (conditions-met conditions planet))))


;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Composite checks to
;; - test whether a given planet meets a variety of conditions.
;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;


(defn habitable?
  "We deem a planet habitable, if it has all minimally good conditions,
  and no fatal conditions."
  [planet]
  (when (and (planet-meets-no-condition?
              fatal-conditions
              planet)
             (planet-meets-all-conditions?
              minimal-good-conditions
              planet))
    planet))

#_(map :pname
       (filter habitable? p/target-planets))

silian05:02:25

Interesting!

silian06:02:33

Never heard of thrush. I'm intrigued. Need to spend some more time with combinators!

itaied10:01:06

hey all, I'm looking for a lightweight cljs web server (like js express). What do you guys recommend?

delaguardo11:01:13

I don't know about cljs server but it should be possible to use the same express via interop

valerauko11:01:54

I made a deno based example with cljs back in the day. It's not up-to-date but maybe you can use it as reference? https://github.com/valerauko/cljs-deno-example

itaied12:01:43

hey thanks, I am currently using express, but I thought perhaps there's a cljs implementation, as there are plenty in clj

delaguardo12:01:44

https://www.surveymonkey.com/stories/SM-_2BH3b49f_2FXEkUlrb_2BJSThxg_3D_3D/ probably because mostly people are using clojure as a backend for clojurescript apps

👍 1
Jim Newton11:01:04

What is the idiomatic way to iterate through a hash map using (for ...) and (doseq ...) ? I'd like to add an example to https://clojuredocs.org/clojure.core/for What I normally do is the following, but it seems I'm doing more work than necessary

(for [k (keys hash)
      :let [v (get hash k)]
  ...

Martin Půda11:01:44

(for [[k v] {:a 1 :b 2}]
  ...)

(doseq [[k v] {:a 1 :b 2}]
  ...)

👍 3
1
Jim Newton15:01:56

I thought I tried that and it failed. it must have failed for some other reason, and I misinterpreted the error messages.