Fork me on GitHub
#beginners
<
2021-06-02
>
rafalw06:06:58

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?

phronmophobic06:06:16

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

Bryce Tichit09:06:02

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

Stuart09:06:24

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 Tichit10:06:20

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-john10:06:16

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

practicalli-john10:06:07

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

practicalli-john10:06:29

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

practicalli-john10:06:46

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

Bryce Tichit10:06:02

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 Tichit10:06:08

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

practicalli-john10:06:38

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

Bryce Tichit10:06:30

Thanks a lot seems really good, will try that

practicalli-john10:06:24

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

3
JohnJ16:06:07

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

delaguardo17:06:13

However not any wsdl can be processed by that lib.

seancorfield17:06:57

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.

seancorfield17:06:19

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

JohnJ17:06:03

@U04V70XH6 yep, looks like the "simplest" approach

JohnJ17:06:17

@U04V4KLKC yeah, need wsdl processing

delaguardo17:06:19

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

JohnJ17:06:09

Oh ok, will take a look, thx

Nom Nom Mousse16:06:04

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

ghadi17:06:32

those are literal tabs in your clojure strings

ghadi17:06:55

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

ghadi17:06:35

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

ghadi17:06:51

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

Nom Nom Mousse17:06:39

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

ghadi17:06:42

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

ghadi17:06:09

via slurp, just not in situ in source code

Jacob Rosenzweig17:06:16

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.

seancorfield18:06:56

We’ve been heavy users of Spec at work since it first appeared (way back in the 1.9 prerelease cycle). This blog post talks about the ways we use it: https://corfield.org/blog/2019/09/13/using-spec/

👍 6
alexmiller17:06:12

they don't replace types

ghadi17:06:07

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

Jacob Rosenzweig17:06:28

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 Rosenzweig17:06:23

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

ikitommi17:06:59

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.

ikitommi18:06:07

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

borkdude18:06:32

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

ikitommi18:06:10

yes, but that could be integrated into editors.

ikitommi18:06:56

also malli->TypeScript might be useful.

Ron Herrema18:06:37

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)))))

dpsutton18:06:36

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

dpsutton18:06:42

#(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 [...] ...)

noisesmith18:06:08

@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?

Michael Stokley18:06:57

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

Michael Stokley18:06:20

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

Ron Herrema21:06:08

@dpsutton Thanks - that’s helpful.

Ron Herrema21:06:45

@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))

noisesmith23:06:57

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

noisesmith23:06:48

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)

noisesmith23:06:40

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

noisesmith23:06:33

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

noisesmith23:06:02

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

R.A. Porter21:06:57

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

Ron Herrema22:06:38

@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 Herrema22:06:45

@coyotesqrl that’s very clear - thanks

dpsutton22:06:29

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

🎯 2
👍 2
dpsutton22:06:42

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

acim122:06:12

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

dpsutton22:06:20

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

Ron Herrema22:06:30

@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.

acim122:06:33

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

dpsutton22:06:29

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

acim122:06:40

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

hiredman22:06:34

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