beginners

Getting started with Clojure/ClojureScript? Welcome! Also try: https://ask.clojure.org. Check out resources at https://gist.github.com/yogthos/be323be0361c589570a6da4ccc85f58f.
rafalw 2021-06-02T06:07:58.234700Z

Hi, lets say I have a module that is designed to handle some data manipulation for a model. I need to have two getter functions, one not safe, if key is missing throws exception, another safe, if key is missing returns nil (get model attr) ;; => throws if attr is not in the model (get-safe model attr) ;; => returns nil if attr is not in the model The logic is that attr in 99% cases should be in the model and if it's not it's an error, second function is for 1% cases for which is ok to no have attr My question is it's creating two separate functions a good approach, if yes is there any naming convention for such case?

phronmophobic 2021-06-02T06:28:16.234800Z

Having two separate functions seems reasonable. get-safe appears to be the same as clojure.core/get so I would just use get. for the version that throws, I would consider some other short names, but try to make sure they are distinct from get so that it's easy to distinguish safe vs unsafe: • get!grabmustpicknabnetclutch

2021-06-02T09:22:50.235100Z

Thank you!

Bryce Tichit 2021-06-02T09:50:02.235900Z

Hey guys, does someone know how to copy the whole block of code I've just written using rebel-readline ? Thanks for the help 🙂

2021-06-02T09:57:24.236Z

You entered the code directly into the repl? You want to not do that. The repl should be the target where the code is evaluated but you should be typing your code into an editor hooked up to your repl.

Bryce Tichit 2021-06-02T10:13:20.236200Z

I understand but for simple use cases I wanted to use the REPL directly, if it's not possible when I will use an editor but seem overkill to me

practicalli-john 2021-06-02T10:34:16.236500Z

Suggest using the command history. Use the up arrow to go back through the history and down arrow to go forward through history

practicalli-john 2021-06-02T10:35:07.236700Z

If you want to copy the code from the terminal into another application, then select it with a mouse 🤷

practicalli-john 2021-06-02T10:38:29.236900Z

Ah, Ctrl-U will kill the line and should put it into the clipboard / kill ring

practicalli-john 2021-06-02T10:40:46.237100Z

but that probably only works if the code is on the same line...

Bryce Tichit 2021-06-02T10:41:02.237300Z

Thanks for the insights ! The problem is that if your input is multi-line this does not work because either rebel add #_ prefix and Ctrl U only kill one line

Bryce Tichit 2021-06-02T10:41:08.237500Z

So yes I think I will use an editor seem easier haha

practicalli-john 2021-06-02T10:42:38.237700Z

I start rebel readline in the terminal along with an nREPL server, so I can connect to that repl process from Clojure editors

Bryce Tichit 2021-06-02T10:43:30.237900Z

Thanks a lot seems really good, will try that

practicalli-john 2021-06-02T10:44:24.238100Z

If you are using Clojure CLI tools to run the REPL, I have example aliases for running rebel and nrepl together https://github.com/practicalli/clojure-deps-edn#repl-terminal-ui

1✅
JohnJ 2021-06-02T16:47:07.245Z

For those that had to talk to SOAP service, how did you go about approaching it from Clojure?

Nom Nom Mousse 2021-06-02T16:57:04.247Z

When I use sh with escaped characters it turns them into literals:

(sh "bash" "-c" "bwa mem -R '@RG\tID:A\tSM:A'  hg19.fasta  | samtools view -Sb - > result.delteme")
gives the error:
[E::bwa_set_rg] the read group line contained literal <tab> characters -- replace with escaped tabs: \\t

2021-06-02T16:58:43.247400Z

https://github.com/xapix-io/paos You could try this library.

2021-06-02T17:02:13.249200Z

However not any wsdl can be processed by that lib.

seancorfield 2021-06-02T17:03:57.251Z

We used wsdl2java to produce Java source classes, and compiled them into a library, which we used directly from Clojure via interop — and then plain HTTP to talk to the SOAP service with the XML those classes produced.

seancorfield 2021-06-02T17:04:19.251500Z

(it was a while back and, luckily, we don’t have to talk to any SOAP services now)

ghadi 2021-06-02T17:08:32.255Z

those are literal tabs in your clojure strings

ghadi 2021-06-02T17:08:55.255800Z

if you want to have SLASH-T, you need to escape the slash in your literal: "\\t"

ghadi 2021-06-02T17:09:35.256700Z

"\t" is clojure syntax for a string containing only tab character, aka ascii/unicode 9

ghadi 2021-06-02T17:09:51.257200Z

"\\t" is a two character string, slash followed by t

Nom Nom Mousse 2021-06-02T17:10:39.258200Z

Understood. So there is nothing like pythons r"\t" (where r denotes a raw string)?

ghadi 2021-06-02T17:11:14.258400Z

no

1🙏
ghadi 2021-06-02T17:11:42.258900Z

if you want a raw string, you can read a file

ghadi 2021-06-02T17:12:09.259700Z

via slurp, just not in situ in source code

Jacob Rosenzweig 2021-06-02T17:13:16.260400Z

Can someone explain to me how exactly specs or malli "replaces" types? I see that they're good descriptive frameworks for specifying, validating, and conforming data but outside of generated tests and markdown documentation (e.g. autodoc can use specs), I don't see how they replace types. I suppose it offers alternatives to types (just use unit tests if you want safety as you develop), but maybe I'm missing something.

alexmiller 2021-06-02T17:16:12.261600Z

they don't replace types

JohnJ 2021-06-02T17:17:03.262200Z

@seancorfield yep, looks like the "simplest" approach

JohnJ 2021-06-02T17:17:17.262600Z

@delaguardo yeah, need wsdl processing

ghadi 2021-06-02T17:18:07.263500Z

x "replaces" y is a big (maybe unnecessarily) assertion, so I'm going to set that aside for a second. there's a short but dense paragraph in the rationale doc that offers some insight into the contrast of spec / types https://clojure.org/about/spec#_expressivity_proof

2021-06-02T17:20:19.264100Z

I made a mistake in my sentence) corrected - not every wsdl can be processed. So you can try yours )

JohnJ 2021-06-02T17:22:09.265400Z

Oh ok, will take a look, thx

Jacob Rosenzweig 2021-06-02T17:45:28.269100Z

One thing I'm starting to see is the use of these DSLs for creating validation "functions". That way you can have sanitizers around your functions without introducing "types" that just result in runtime errors (as opposed to here where a wrong "type" could result in anything you want: an error, a default value, etc.

;; Spec
(s/def :clean-event/user (s/keys ...))
(s/def :raw-event/user (s/nilable :clean-event/user))
(s/def :event/cleaned? boolean?)

(defmulti event-cleaned? :event/cleaned?)
(defmethod event-cleaned? false [_]
  (s/keys :req [:event/cleaned? :raw-event/user]))
(defmethod event-cleaned? true [_]
  (s/keys :req [:event/cleaned?] :opt [:clean-event/user]))

(s/def :event/event (s/multi-spec event-cleaned? :event/cleaned?))
;; Code cleanup
(defn cleanup [{:raw-event/keys [user] :as event}]
  (-> event
      (dissoc :raw-event/user)
      (assoc :event/cleaned? true)
      (assoc-unless-nil
        :clean-event/user user)))

Jacob Rosenzweig 2021-06-02T17:46:23.269900Z

Is this very common? For me, this seems like a more flexible alternative to appending ::pre and ::post to all my functions.

ikitommi 2021-06-02T17:57:59.270100Z

tools like clj-kondo can push the spec/malli function validation to static analysis phase, but they are still not types. Would love to see more developer tooling for these. Intellisense/autocompleting based on specs/schemas etc.

ikitommi 2021-06-02T18:00:07.270300Z

just looked at typehole, a VSCode plugin for creating TypeScript definitions from sample data. Wound be easy to do with Clojure.

borkdude 2021-06-02T18:00:32.270800Z

didn't you already support something like this with malli?

ikitommi 2021-06-02T18:00:35.270900Z

https://github.com/rikukissa/typehole

ikitommi 2021-06-02T18:01:10.271300Z

yes, but that could be integrated into editors.

ikitommi 2021-06-02T18:02:56.271400Z

also malli->TypeScript might be useful.

2021-06-02T18:17:43.272100Z

@rob370 it's an old lisp idiom to name something foo' if it's an extended or enhanced version of foo, and to name it foo* if it's a less featureful intermediate form of foo in clojure it seems to only get used for operations that upgrade to bignum:

)user=> (->> (all-ns) (mapcat (comp keys ns-publics)) (map name) (filter #(re-find #"'$" %)))
("+'" "dec'" "inc'" "-'" "*'")

2021-06-02T18:18:29.272300Z

but I've definitely used x' in let blocks to mean "next value of x"

Ron Herrema 2021-06-02T18:50:37.276200Z

This code works fine in terms of doing the job, but I wonder if there is a more terse route to the same goal? Also, it’s not really clear to me when it is necessary to use the # before a function in these kinds of configurations. I realise that makes it an anonymous function, but I don’t yet understand when this is called for. (def nos (take 20 (repeatedly #(+ 24 (rand-int 60)))))

dpsutton 2021-06-02T18:52:36.276900Z

a clarification that might help, # never goes before a function. It is a reader macro that creates a function out of the following form

dpsutton 2021-06-02T18:53:42.278100Z

#(inc %) is just shorthand for (fn [x] (inc x)). Steer clear of it until that makes more sense if you like. When you need a function just start with (fn [...] ...)

2021-06-02T18:58:08.279200Z

@rherrema I'm not sure I understood your question - is it about when you need a function vs. just inline code, or about the anonymous function syntax itself?

2021-06-02T18:58:57.279500Z

the code works fine... but you want it terser?

2021-06-02T18:59:20.280Z

i would expect the opposite, ie although the code may work fine, let's make it more expressive

Ron Herrema 2021-06-02T21:50:08.281400Z

@dpsutton Thanks - that’s helpful.

Ron Herrema 2021-06-02T21:55:45.283100Z

@noisesmith what I’m wondering is why I can’t simply write (+ 24 (rand-int 60)) in that location and instead need to write #(+ 24 (rand-int 60))

R.A. Porter 2021-06-02T21:58:57.284600Z

repeatedly wants a function. (+ 24 (rand-int 60)) is a number, not a function.

Ron Herrema 2021-06-02T22:00:38.286Z

@michael740 that is an interesting perspective - I suppose I am accustomed to working in DSL’s where there’s an attempt to create as direct and efficient a mapping as possible between concept and the expression in code - ‘expressive’ can be construed in multiple ways. As a composer who does live coding of music, using terse but clear forms of code is super valuable.

Ron Herrema 2021-06-02T22:01:45.286300Z

@coyotesqrl that’s very clear - thanks

dpsutton 2021-06-02T22:05:29.287700Z

if you want it more expressive, you can name the random function (defn random-character [] (+ 24 (rand-int 60)) so that your expression where you use it reads a bit more human friendly: (take 20 (repeatedly random-character)) or whatever domain object you are using

1👍1🎯
dpsutton 2021-06-02T22:07:42.289800Z

depends on what you think expressive is. people might consider Clojure expressive since the code to do this is so concise and has built-ins that are ready for it: (take 20 (repeatedly #(+ 24 (rand-int 60)))) or might think more human readable, mapping onto the domain is expressive. in addition to lots of other conceptions of expressive that might be out there

acim1 2021-06-02T22:10:12.292Z

Is there something akin to group-by (without the grouping, rather acting like repeated applications of assoc ) that could do something like

(reduce (fn [m things] (assoc m (:name thing) thing)) {} things)
In a more concise way? In other words, make a map by keying off a particular attribute? Seems like a super common thing to do, but I didn't spot anything handy for it

dpsutton 2021-06-02T22:12:20.293200Z

can you give a representative input and output? having a hard time conceiving what is expected

Ron Herrema 2021-06-02T22:13:30.295300Z

@dpsutton I like your idea of naming the random function (I would call it random-note, as I’m in the domain of music). And yes, ‘expressive’ is conceived in many ways in various domains. That’s why, coming from music, I find it fascinating to hear it used in the context of programming.

acim1 2021-06-02T22:15:33.296900Z

In: [{:name "Bob" :job "Janitor"} {:name "Jill" :job "Accountant"}] Out: {"Bob" {:name "Bob" :job "Janitor"} "Jill" {:name "Jill" :job "Accountant"}} In this case, the names could expected to be unique...more like the attribute is :id

acim1 2021-06-02T22:15:42.297200Z

^^^ @dpsutton

dpsutton 2021-06-02T22:17:29.297900Z

(into {} (map (juxt :name identity) coll) is a common idiom for this.

acim1 2021-06-02T22:18:40.298500Z

Oh...that's helpful. I think that's definitely nicer. Thanks @dpsutton

2021-06-02T22:25:34.299100Z

or just get used to using group-by, which works for non-primary keys as well

2021-06-02T22:35:13.299400Z

see also index-by https://weavejester.github.io/medley/medley.core.html#var-index-by

2021-06-02T23:13:57.299800Z

because repeatedly requires an argument that can be called, (+ 24 (rand-int 60)) simplifies to a number, calculated once and reused (try this with repeat). the #() syntax creates a function that executes the body again each time you call it

2021-06-02T23:16:48.300Z

so instead of asking clojure to calculate a number, you are asking it to create a special kind of object (a function) which instead of doing the work now, does it later when asked for (and redoes the task each time, which is useful with side affects like randomness)

2021-06-02T23:18:40.300200Z

if repeatedly was a macro it could create the function for you, but it's much more useful for it to be a function taking another function

2021-06-02T23:27:33.300400Z

(defmacro rp
  [& body]
  `(let [f# (fn f# []
              (lazy-seq (cons (do ~@body)
                              (f#))))]
     (f#)))
user=> (take 10 (rp (rand-int 10)))
(7 4 8 3 4 3 8 1 3 1)

2021-06-02T23:29:02.300600Z

this is less flexible than repeatedly, it can't be used as a building block without making another macro (or using it inside a macro), unlike functions which can be used as values