beginners

Getting started with Clojure/ClojureScript? Welcome! Also try: https://ask.clojure.org. Check out resources at https://gist.github.com/yogthos/be323be0361c589570a6da4ccc85f58f.
Nazar Trut 2020-09-22T00:18:02.249Z

(defn loop-over-sets
  [expr kb]

  (filter #(and (= (first %) 'if) (= (second %) expr)) kb)

  )
Im getting a dont know how to create from ISEQ error when my expr = 'a and when my kb = '(if a b)

Nazar Trut 2020-09-22T00:18:07.249200Z

does anyone know why?

Nazar Trut 2020-09-22T00:18:33.249700Z

this works when i have kb = '((if a b)(if b c))

seancorfield 2020-09-22T00:22:37.250900Z

Having just '(if a b) as kb doesn't sound right based on what you've described: that's a sequence of just if, a, and b. I think you want kb to be '((if a b)) i.e., a sequence of expressions.

seancorfield 2020-09-22T00:23:27.251800Z

(and the reason for the error is that filter will run that predicate over if, a, and b and the predicate calls first and second which aren't valid on symbols (but are valid on sequences).

seancorfield 2020-09-22T00:23:31.252Z

^ @ntrut

Nazar Trut 2020-09-22T00:25:50.252700Z

hmmm is there a way to change '(if a b) to '((if a b)) in code?

Nazar Trut 2020-09-22T00:26:00.252900Z

@seancorfield

João Galrito 2020-09-22T00:31:18.253100Z

I think you should probably rethink how you're calling that function

João Galrito 2020-09-22T00:31:39.253700Z

are you trying to loop over a list of expressions and return the results for each?

Nazar Trut 2020-09-22T00:31:41.253900Z

its because kb can be both

Nazar Trut 2020-09-22T00:33:35.255600Z

yeah so, What i want to happen is, if kb is ((if a b)(if b c)) and expr is 'a. It would look for "if a" and then it would derive b. And if kb is just '(if a b), then it would still derive b

Nazar Trut 2020-09-22T00:34:55.256400Z

I think if i do if statement saying if first = 'if, then derive the 2nd nth else use that filer function

Nazar Trut 2020-09-22T00:35:01.256600Z

it would work

Nazar Trut 2020-09-22T00:35:14.256800Z

filter*

João Galrito 2020-09-22T00:39:21.259Z

don't know if there's a better way to do it, but you can (if (list? (first kb)) kb (list kb)) I guess

seancorfield 2020-09-22T00:43:33.259900Z

It's not good practice to have a data structure that can be different shapes like that. kb should always be a list of expressions, even if it has just one expression in it.

seancorfield 2020-09-22T00:43:57.260100Z

See https://stuartsierra.com/2015/06/10/clojure-donts-heisenparameter

seancorfield 2020-09-22T00:44:11.260600Z

(that whole series of do's and don'ts is worth reading)

Nazar Trut 2020-09-22T00:45:15.261400Z

I understand what im doing wrong but how do i make sure that kb is '((if a b)) and not '(if a b) when being passed in

seancorfield 2020-09-22T00:46:26.262100Z

You should solve that problem at the source of where kb is being created -- and ensure it is always a sequence of expressions and never a single expression.

Nazar Trut 2020-09-22T00:47:12.262900Z

is there a way to change kb to '((if a b))

Nazar Trut 2020-09-22T00:47:14.263100Z

?

seancorfield 2020-09-22T00:47:21.263300Z

In other words, your "loop over sets" (terrible name 🙂 ) should always just assume it is passed a sequence of expressions.

seancorfield 2020-09-22T00:47:40.263800Z

It should not be trying to accommodate someone calling it with invalid data.

seancorfield 2020-09-22T00:48:17.264400Z

How/where is kb created? That's where you should fix the problem.

Nazar Trut 2020-09-22T00:48:48.265Z

(defn fwd-infer
  [prob kb]

  (cond
    (= (first prob) 'and) (fwd-infer (first (and-elimination prob)) #(and-elimination prob))
    (= (first prob) 'not) (fwd-infer (elmination-not prob) #(elmination-not prob))
    (= (first prob) 'if) (fwd-infer (modus-ponens (nth prob 1) kb) #(modus-ponens (nth prob 1) kb))
    )

  )
This is where kb is created

Nazar Trut 2020-09-22T00:49:17.265300Z

It is just passed through my infer function

João Galrito 2020-09-22T00:49:50.265700Z

so it's the and case that might return just one element

João Galrito 2020-09-22T00:53:11.267100Z

but you're not creating kb there

João Galrito 2020-09-22T00:53:15.267400Z

you're just passing it

Nazar Trut 2020-09-22T00:53:52.267900Z

For my homework, we just simply use the REPL to pass our data in

João Galrito 2020-09-22T00:54:23.268400Z

so it is a requirement that it accepts both '(if a b) and '((if a b) (if c b))?

João Galrito 2020-09-22T00:54:40.268700Z

i'd say just transform the data when you read it

João Galrito 2020-09-22T00:55:09.269600Z

if the first form is not a list, wrap everything in a list

Nazar Trut 2020-09-22T00:55:20.269800Z

There is maybe a better way to do this but this is the only way i could think of doing it

Nazar Trut 2020-09-22T00:55:25.270Z

Ok ill try that

João Galrito 2020-09-22T00:58:36.271500Z

or you add an 'or and do '(if (or a c) b) 😛

Nazar Trut 2020-09-22T00:59:01.271700Z

how would i wrap everything in a list

João Galrito 2020-09-22T00:59:06.271900Z

(list x)

João Galrito 2020-09-22T00:59:20.272100Z

i gave you the code earlier

misto quente 2020-09-22T01:13:46.277100Z

hello, I have a lein project i’m trying to add shadow-cljs to, first time using it (I’m working from the book Web Dev with Clojure) I have, src/clj and src/cljs folders each with a guestbook.core namespace. Could this be causing an issue? I am getting this error trying to start up shadow-cljs, I’ve looked over my project.clj and shadow-cljs.edn a few times and nothing’s jumping out at me as incorrect…

[:app] Configuring build.
[:app] Compiling ...
[:app] Build failure:
The required namespace "guestbook.core" is not available, it was required by "guestbook/app.cljs".
"guestbook/core.clj" was found on the classpath. Should this be a .cljs file?

seancorfield 2020-09-22T01:26:02.277400Z

Your <http://guestbook.app|guestbook.app> namespace is requiring guestbook.core -- but <http://guestbook.app|guestbook.app> is ClojureScript and guestbook.core is Clojure, based on the extensions.

seancorfield 2020-09-22T01:27:30.277600Z

If both src/clj and src/cljs are on your classpath, you can't have the same named file in two places -- only one of them will be found.

seancorfield 2020-09-22T01:27:45.277800Z

So it's finding src/clj/guestbook/core.clj

1👍
seancorfield 2020-09-22T01:28:37.278Z

ClojureScript apps do tend to have .clj files in them for macros (since ClojureScript relies on Clojure for macro definition and expansion, prior to compilation -- to JavaScript).

João Galrito 2020-09-22T01:30:21.279400Z

what is the best way to redefine a binding in an outer scope, for ex:

(let [x 1]
   (do-something-to-x-to-make-it 2)
   (+ x 1))

2020-09-22T01:31:18.279800Z

You cannot

misto quente 2020-09-22T01:31:51.280900Z

thanks. just tried renaming the cljs file and ns, but it’s still not finding it..must be something else im not seeing

João Galrito 2020-09-22T01:31:55.281300Z

what could be the approach? I'm traversing a tree and need to maintain some state

2020-09-22T01:32:24.282300Z

Local bindings (established via function application or let) are immutable

2020-09-22T01:33:03.283400Z

Look at tree traversals in functional languages

2020-09-22T01:33:29.283900Z

General you use recursive function calls

2020-09-22T01:35:09.286200Z

That style is what works in clojure, but sometimes you use loop/recur in place of recursive function calls, but it will have the same shape as a recursive function call style

João Galrito 2020-09-22T01:37:44.286500Z

i'm traversing the tree alright

dpsutton 2020-09-22T01:38:09.287100Z

an example of tree traversal using an atom: https://github.com/clojure/clojurescript/blob/master/src/main/clojure/cljs/util.cljc#L241

João Galrito 2020-09-22T01:38:11.287200Z

the problem is i want to pass some state between sibling branches

João Galrito 2020-09-22T01:38:47.287800Z

I know about atom, thought it was just used for concurrency stuff

dpsutton 2020-09-22T01:39:49.288400Z

its a threadsafe mutable container.

2020-09-22T01:41:25.289300Z

You can return information from traversals and pass it to sibling traversals

seancorfield 2020-09-22T01:41:47.289400Z

Are you getting that same error, or a different one?

João Galrito 2020-09-22T01:42:11.289700Z

yeah, that's what i was thinking

dpsutton 2020-09-22T01:42:56.290200Z

http://sarabander.github.io/sicp/html/1_002e2.xhtml#g_t1_002e2_002e1 (i always link to this nicely formatted version)

João Galrito 2020-09-22T01:44:04.291400Z

in this case i'm traversing the tree using a multimethod

João Galrito 2020-09-22T01:44:15.291900Z

so i should wrap it in another function that deals with the state

João Galrito 2020-09-22T01:44:21.292400Z

and call the multimethod inside that

2020-09-22T01:44:46.293200Z

https://github.com/hiredman/qwerty/blob/master/expand.clj#L313 is a rather complicated function (a macro expander for another programming language) that walks a tree (expressions in the language) and passes information down and up and around

seancorfield 2020-09-22T01:44:49.293400Z

The error above is that it is finding guestbook.core but it's expecting a .cljc or .cljs extension on it.

João Galrito 2020-09-22T01:45:40.293900Z

@hiredman that's more or less what I'm doing here

dpsutton 2020-09-22T01:47:56.295Z

the "trick" to make sure your state goes into the siblings is to use a reduce to keep accumulating state between the siblings.

João Galrito 2020-09-22T01:58:51.296300Z

ok, my issue is that now I have to deal with state at every node when the majority of them don't care about it

misto quente 2020-09-22T01:59:44.296600Z

oof…i was missing :source-paths [..., "src/cljs"] in my project.clj … build completed now with the original filenames maintained, there’s a core.clj and a core.cljs using the same ns in the project. Not sure if i’ll run into issues down the line…

misto quente 2020-09-22T02:04:31.296900Z

i was using lein cljsbuild previously, and removed that config entirely which had its own source path prop

João Galrito 2020-09-22T02:16:15.297500Z

I think atom is what I want here

Nazar Trut 2020-09-22T03:02:52.299200Z

(defn loop-over-sets
  [expr kb]

  (filter #(and (= (first %) 'if) (= (second %) expr)) kb)
  )
This gives me the correct output with (if a b)(if b c) as kb and b as expr but when i have kb = (if a b) a (if b c) and my expr = b, I want to get (if b c) but im getting a "Don't know how to create ISeq from: clojure.lang.Symbol"

Nazar Trut 2020-09-22T03:03:42.300200Z

@joao.galrito Do you know if i could change this function so it can read both ((if a b)(if b c)) AND '((if a b) a (if b c))

João Galrito 2020-09-22T03:18:32.300700Z

what do you want to do with the 'a in the middle

João Galrito 2020-09-22T03:18:33.301Z

ignore it?

Nazar Trut 2020-09-22T03:18:46.301300Z

yeah

Nazar Trut 2020-09-22T03:18:55.301600Z

because this function only looks for ifs

Nazar Trut 2020-09-22T03:19:31.302200Z

it should look for ifs

João Galrito 2020-09-22T03:20:16.302700Z

then you need to add another condition to your filter predicate

João Galrito 2020-09-22T03:20:31.303100Z

to check if what you're reading is a list

João Galrito 2020-09-22T03:23:18.303700Z

because right now you're trying to call (first 'a)

Nazar Trut 2020-09-22T03:23:26.303900Z

i see now

Nazar Trut 2020-09-22T03:24:25.304200Z

I figured it out

Nazar Trut 2020-09-22T03:24:30.304400Z

I think

seancorfield 2020-09-22T03:26:32.304800Z

sequential? is probably the predicate you want here.

Nazar Trut 2020-09-22T03:26:37.305Z

nvm i havent got it

Nazar Trut 2020-09-22T03:27:05.305800Z

haha this is hard

Nazar Trut 2020-09-22T03:27:13.306200Z

Problems only lead to more problems

João Galrito 2020-09-22T03:27:14.306400Z

@seancorfield any particular reason? If just working with lists

seancorfield 2020-09-22T03:27:34.307100Z

(filter #(and (sequential? %) (= (first %) 'if) (= (second %) expr)) kb)

João Galrito 2020-09-22T03:27:36.307200Z

(instead of list? for example)

seancorfield 2020-09-22T03:28:51.307600Z

@joao.galrito

user=&gt; (list? ())
true
user=&gt; (list? (range 5))
false
user=&gt; (list? (map inc (range 5)))
false
user=&gt; (sequential? ())
true
user=&gt; (sequential? (range 5))
true
user=&gt; (sequential? (map inc (range 5)))
true
user=&gt;

João Galrito 2020-09-22T03:29:13.308300Z

gotcha

João Galrito 2020-09-22T03:29:23.308800Z

if at any point it turns into a lazy list it breaks

seancorfield 2020-09-22T03:29:24.308900Z

list? is very specific and if Nazar is building the kb programmatically, he might get sequences that are not lists.

João Galrito 2020-09-22T03:30:33.309300Z

he said kb is coming from the REPL

seancorfield 2020-09-22T03:30:59.310100Z

But he's processing it with fwd-infer for the reductions?

João Galrito 2020-09-22T03:31:19.310600Z

but even then just using filters and maps can result in a lazyseq

seancorfield 2020-09-22T03:33:41.311600Z

Yup. Test for the abstraction, not the concrete type.

seancorfield 2020-09-22T05:21:23.312100Z

Glad you got it working, and thank you for reporting back with the solution!

suren 2020-09-22T06:15:26.313300Z

I am using following code to create an empty file.

(defn create-seed [name]
  (with-open [wtr (io/writer (get-seed-file-name name))]
    (.write wtr "")))
Is there a shorter version?

suren 2020-09-22T06:16:28.314200Z

I was hopping to have something as concise as touch afile.txt

seancorfield 2020-09-22T06:16:33.314400Z

@suren Using spit...

seancorfield 2020-09-22T17:17:24.376700Z

@suren You get used to reading (Java) stack traces but they can be daunting at first.

suren 2020-09-26T05:10:47.135300Z

Yeah too much noise in the stack traces.

suren 2020-09-22T06:16:58.314700Z

Thanks I will try it.

seancorfield 2020-09-22T06:17:09.315Z

(spit (get-seed-file-name name) "")

seancorfield 2020-09-22T06:18:05.315200Z

It's a built-in version of the code you posted:

user=&gt; (source spit)
(defn spit
  "Opposite of slurp.  Opens f with writer, writes content, then
  closes f. Options passed to <http://clojure.java.io/writer|clojure.java.io/writer>."
  {:added "1.2"}
  [f content &amp; options]
  (with-open [^java.io.Writer w (apply jio/writer f options)]
    (.write w (str content))))
nil
user=&gt; 

seancorfield 2020-09-22T06:18:57.315400Z

Now I see the source, I guess you could even do (spit (get-seed-file-name name) nil) which might be more explicit?

seancorfield 2020-09-22T06:19:06.315600Z

Since (str nil) is ""

suren 2020-09-22T06:19:24.315800Z

Thanks @seancorfield it worked like a charm

suren 2020-09-22T06:20:02.316Z

I wish we had a karma coin, so that I could give some to you. 🙂

dpsutton 2020-09-22T06:22:11.316200Z

(.createNewFile (io/file "bob"))

dpsutton 2020-09-22T06:22:40.316400Z

read javadocs for File and there's something that could help right away

seancorfield 2020-09-22T06:23:27.316600Z

Also, what do you want to happen if the file already exists?

suren 2020-09-22T06:24:16.316800Z

Just error out.

seancorfield 2020-09-22T06:25:32.317Z

(spit ... nil) will overwrite it with an empty string; (spit ... nil :append true) would append an empty string to an existing file (i.e., leave it unchanged apart from the mod date/time).

seancorfield 2020-09-22T06:25:47.317200Z

I suspect .createNewFile would error out if it already exists

seancorfield 2020-09-22T06:26:36.317400Z

Ah, no, it would just return false

seancorfield 2020-09-22T06:26:48.317600Z

(and it returns true if it successfully creates the file)

suren 2020-09-22T06:27:16.317800Z

io/file is very confusing. I am using following code to list the files in a directory.

(-&gt;&gt; (io/file "resources/seeds")
      (.list))
As @dpsutton pointed out (.createNewFile (io/file "bob")) creates a new file. What exactly is (io/file 'path)' ?

seancorfield 2020-09-22T06:27:40.318Z

user=&gt; (.createNewFile (<http://clojure.java.io/file|clojure.java.io/file> "/tmp/foo"))
true ; file did not exist, this created it
user=&gt; (.createNewFile (<http://clojure.java.io/file|clojure.java.io/file> "/tmp/foo"))
false ; file existed so it was not touched
user=&gt; 

seancorfield 2020-09-22T06:28:01.318200Z

io/file creates a <http://java.io|java.io>.File object so you'll have to read the Java API docs for that.

seancorfield 2020-09-22T06:28:13.318400Z

But it's basically an abstraction for a file.

seancorfield 2020-09-22T06:28:40.318600Z

So it can be a file that doesn't exist, a directory, a file that does exist, or any implementation of something that can behave like a file.

suren 2020-09-22T06:28:59.318800Z

hmm got it

suren 2020-09-22T06:29:32.319Z

For my use case I will use (.createNewFile

seancorfield 2020-09-22T06:29:56.319200Z

Java has dramatically improved its support for file ops in more recent versions, so now there's a java.nio.* package hierarchy with abstractions for paths and files and entire file systems 🙂

seancorfield 2020-09-22T06:30:14.319400Z

Clojure pretty much just delegates to Java interop for file stuff.

suren 2020-09-22T06:36:19.319600Z

Yeah I can see that. I think clojure's biggest weakness is its error messages.

suren 2020-09-22T06:37:05.319800Z

Most of the time the relevant files are buried somewhere in the middle of stack trace.

teodorlu 2020-09-22T07:03:37.320200Z

Another line of thinking: reduce over your list of filters. Relevant if you need to control how filters are applied.

2020-09-22T07:14:06.320600Z

[fn-A fn-B fn-C] is in a vec, is it possible to use (every-pred [fn-A fn-B fn-C]) this way?

v3ga 2020-09-22T07:21:00.322200Z

Not sure if this is the best section to ask but for a serious web project would you go with ring/reitit or pedestal?

2020-09-22T07:24:11.322300Z

Yes, it is possible, but via apply. (apply every-pred [fn-A fn-B fn-C])

2020-09-22T07:24:49.322500Z

Great, Thank you.

Ilari Tuominen 2020-09-22T08:09:05.325300Z

@decim both are definitely for serious projects. I would go with ring/reitit but it’s because I’m more familiar with it and my colleagues are more inclined to it. I’d still go through both documentations and decide which is more to your liking. Also reitit comes with swagger support out of the box but that functionality is easily available for pedestal as well so they’re not too far apart. I’m still very much a clojure beginner so somebody with actual production experience with both should chime in.

Ilari Tuominen 2020-09-22T08:09:40.325500Z

I would check the reitit faq for some explanation of the differences https://github.com/metosin/reitit/blob/master/doc/faq.md

Risetto 2020-09-22T08:25:54.328300Z

Assuming a function has too take two arguments, but you want one of them to just not do anything, is just doing () an ok option? I only want to log something based on a if-dev macro which I am unable to modify

Risetto 2020-09-22T08:26:45.328700Z

actually I think nil is better now that I think of it

Risetto 2020-09-22T10:47:01.331500Z

Are there any clever clojure function to convert booleans to 0/1?

2020-09-23T08:55:09.045300Z

Maybe you want a default result:

(defn judge [x] 
  (get {true 1 false 0} x -1))

2020-09-23T08:56:36.045500Z

maps have the same interface as get) ({true 1 false 0} x -1) also will give you default value

2020-09-23T08:58:47.045700Z

Thank you @delaguardo, I have read from somewhere that only get can append a default value.

2020-09-22T10:49:00.331600Z

like if? (if x 1 0)

Risetto 2020-09-22T10:49:56.331800Z

Yea, but the other way around 🙂

Risetto 2020-09-22T10:50:09.332Z

Oh nvm 😄

Risetto 2020-09-22T10:50:14.332200Z

Thank you

2020-09-22T11:40:28.333400Z

({true 1 false 0} x)

1👍
AM 2020-09-22T11:52:54.333800Z

practicalli-john 2020-09-22T12:03:01.334Z

http://practicalli.github.io/clojure/testing/unit-testing/ has a simple guide to writing unit tests using Clojure CLI. With exercism projects you can add a deps.edn file to the root of a project after its created by the exercism command. This just needs the src and test paths and a test runner alias. As others suggested, you can also use a user wide configuration, saved in -/clojure/deps.edn which applies to all Clojure projects you run with the Clojure CLI tool (clj, clojure) Both Sean and I have examples of the user wide deps.edn configuration with lots of examples https://github.com/practicalli/clojure-deps-edn

practicalli-john 2020-09-22T12:04:06.334300Z

The congnitect labs test runner is a nice simple test runner to start with. I do recommend looking at Kaocha once you start building bigger projects.

michele mendel 2020-09-22T12:40:53.337100Z

Why are

(apply * []) equal 1
(apply + []) equal 0
? Has it anything to do with 1 and 0 being the identities of monoids with operators * and +?

alexmiller 2020-09-22T12:42:43.337300Z

Yes

João Galrito 2020-09-22T14:24:53.340Z

does doseq not realize a lazy seq?

alexmiller 2020-09-22T14:29:10.340200Z

no

alexmiller 2020-09-22T14:29:20.340500Z

sounds like doall ?

João Galrito 2020-09-22T14:30:27.340900Z

so doall / dorun + map for iterating over a seq for side effects?

João Galrito 2020-09-22T14:30:48.341200Z

thought that was what doseq was for

João Galrito 2020-09-22T14:32:38.341500Z

I see there's also run!

teodorlu 2020-09-22T14:40:08.342900Z

doseq does iterate over lazy sequences, but it doesn't return anything. Use it when you need the side effects, but don't care about the values!

João Galrito 2020-09-22T14:40:46.343300Z

I tried doseq but nothing happened

alexmiller 2020-09-22T14:41:35.343600Z

if you want to iterate for side effects, I would recommend run!

teodorlu 2020-09-22T14:41:50.343700Z

What did you try? Here's an example:

user=&gt; (doseq [x (range 5)] (prn x))
0
1
2
3
4
nil

João Galrito 2020-09-22T14:47:24.343900Z

(doseq [operations chunks]
      (http/post
        (str (env :es-host) "/_bulk")
        {:body (str (string/join \newline operations) \newline)
         :content-type :json})
      (println "Inserted " (count operations)))

João Galrito 2020-09-22T14:47:42.344100Z

where chunks is

(-&gt;&gt; documents
                    (map #(str (json/write-str {:index {:_index collection-name
                                                        :_id (get % id-key)}})
                               \newline
                               (json/write-str %)))
                    (partition-all max-chunk-size))

João Galrito 2020-09-22T14:49:04.344700Z

oh, I think doseq realizes the sequence before executing the body

João Galrito 2020-09-22T14:50:38.345400Z

anyway in my case I can take advantage of parallelism so ended up using pmap and dorun

Ory Band 2020-09-22T15:09:26.349100Z

Hi all. I have a design question: I have this map object, which serves as the main "data model" I use in my app. I pass it around, modify it, bla bla. In addition, in order to make it easier to update it without having to break changes in the places that use this map, I ended up encapsulating it with constructor, update and getter functions. something like this:

; Constructor
(defn generate-my-model
  [id]
  {:id      id
   :field-a []
   :field-b []})

; Updater / setter
(defn update-my-model
  ([id a b]
   (assoc (generate-my-model id)
          :field-a a
          :field-b b)))

; Getters
(defn my-model-&gt;id [m] (:id m)
(defn my-model-&gt;a [m] (:a m)
(defn my-model-&gt;b [m] (:b m)
I emphasize: these "encapsulation" functions exist to make it easier to update my code later on without breaking things for outside users of my library. Otherwise, everywhere i had to read that map's fields i would have to use the field name explicitly. The getters and updater allows me to hide these implementation details from the outside world. My question: Is there a better way to do this? I hate writing all these getters and updaters, it's just ugly boilerplate code. In OOP-land these things are a must due to the same reason (encapsulation). But in Clojure .. i dunno, i remember Rich Hickey saying it's ok to pass data maps around. I just don't like the cost of it. It will create a headache for me every time i want to change the implementation of this entity map. 😬 Wdyt?

phronmophobic 2020-09-23T16:36:08.058800Z

I'm not generally against having helper functions that provide a way to explicitly list what the common operations are, but I would warn against pretending that it "encapsulates" your data. I also wouldn't start with defining getters/setters and would only consider adding them on a case by case basis. In my experience, getters/setters provide little benefit for data at rest or being sent over the network which is the important part to get right for maintaining long running programs and systems To address "what keys?" and "what entity?", I would just use spec. As he mentions in the talk, it's meant to address these problems. The big difference between spec's approach and defining getters/setters is that spec is data focused and getters/setters are operations focused. The differences between a data specification and an operational specification are really critical. Most programs would be better served by focusing on the data than on the operations. Depending on your domain, it may be useful to use something like specter or getters/setters to help declutter deeply nested code. In my experience, there are many domains where an extra layer of abstraction is unnecessary. I really wish computer science would be renamed data-ology.

2020-09-23T16:58:02.059Z

i think this is an interesting discussion. i don't have anything to add except to note that re-frame basically has getters and setters on a big map (`app-db`) in the form of subscriptions and events. for a while i really felt like i was back in oop land, writing a getter and setter for each piece of data in app-db. in that context, i do think there's something to be said for separating out the data from "exactly where in this huge nested map does it belong"

2020-09-23T16:58:28.059200Z

i've run into many snags trying to reorganize the structure of app-db and unknowingly breaking code that depended on that exact configuration

phronmophobic 2020-09-23T17:05:00.059500Z

I think if you're running into "what's the shape of the data supposed to be?", I would reach for spec or defining the data specification rather than getters/setters

1👍
phronmophobic 2020-09-23T17:05:39.059800Z

it would be great to hear other's thoughts on the subject though

1👍
phronmophobic 2020-09-22T15:35:33.349500Z

it depends on what you're trying to model so you may need to provide more context for a better answer. if you have getters that are simply just wrappers around specific keys, then they add a layer of indirection for little value. instead of getters/wrappers, it's may be worth creating protocols or multimethods that can provide an interface to your data model eg:

(defprotocol IShape
  (area [this])
  (perimeter [this]))

(defrecord Circle [r]
  IShape
  (area [this] (* Math/PI r r))
  (perimeter [this] (* 2 Math/PI r)))

(defrecord Square [l]
  IShape
  (area [this] (* l l))
  (perimeter [this] (* l 4)))

(area (-&gt;Circle 2))
(perimeter (-&gt;Square 4))

Ory Band 2020-09-22T15:46:28.349800Z

Thanks. Thing is, i don't really need polymorphism here. Just some sort of accessors to set and get fields while hiding the implementation.

phronmophobic 2020-09-22T15:47:35.350Z

protocols are the way to separate what from how (ie. hiding the implementation)

Ory Band 2020-09-22T16:16:26.350200Z

ok. what about accessors? the setters

Ory Band 2020-09-22T16:16:31.350400Z

how to set stuff.

2020-09-22T16:22:03.350800Z

Hello again

2020-09-22T16:22:47.351600Z

I'm playing with a deps.edn project and I'm having some difficulty starting up my REPL properly

2020-09-22T16:23:56.352200Z

So, here's my deps.edn file:

{:paths ["src"]
 :deps {org.clojure/clojure                  {:mvn/version "1.10.1"}
        com.datomic/datomic-free             {:mvn/version "0.9.5697"}
        com.datomic/client-pro               {:mvn/version "0.9.63"}
        org.postgresql/postgresql            {:mvn/version "9.3-1102-jdbc41"}
        http-kit                             {:mvn/version "2.4.0"}
        metosin/reitit                       {:mvn/version "0.5.5"}}
 :aliases {:server      {:main-opts   ["-m" "grok.core"]}
           :dev         {:extra-paths ["config/dev" "env/dev"]}
           :test        {:extra-paths ["test" "config/test"]
                         :extra-deps  {lambdaisland/kaocha {:mvn/version "0.0-529"}
                                       lambdaisland/kaocha-cloverage {:mvn/version "1.0.63"}}
                         :main-opts   ["-m" "kaocha.runner"]}
           :socket-repl {:jvm-opts ["-Dclojure.server.repl={:port,50505,:accept,clojure.core.server/repl}"]}}}

2020-09-22T16:24:43.352900Z

When I do clj -A:dev:socket-repl, I get a REPL in the namespace user

phronmophobic 2020-09-22T16:25:27.353800Z

(defprotocol IShape
  (area [this])
  (perimeter [this]))

(defprotocol IScale
  (scale [this s]))

(defrecord Circle [r]
  IShape
  (area [this] (* Math/PI r r))
  (perimeter [this] (* 2 Math/PI r))
  IScale
  (scale [this s] (-&gt;Circle (* r s))))

(defrecord Square [l]
  IShape
  (area [this] (* l l))
  (perimeter [this] (* l 4))
  IScale
  (scale [this s] (-&gt;Square (* l s))))

(area (-&gt;Circle 2))
(perimeter (-&gt;Square 4))
(scale (-&gt;Square 2) 2) 

2020-09-22T16:25:32.354100Z

However, I can't do (ns com.my-project.core) and have any definitions in said namespace be loaded in the REPL

phronmophobic 2020-09-22T16:25:32.354200Z

see IScale

dpsutton 2020-09-22T16:25:58.354700Z

that declares a namespace. you probably want (require 'com.my-project.core)

phronmophobic 2020-09-22T16:26:27.354800Z

the idea is to have a stable interface that's independent of the implementation. it can be worth it even in cases where there's just one implementation.

Ory Band 2020-09-22T16:26:32.355Z

I see. This forces me to declare a record even though i only have a single record type. That is, no square, just a circle.

Ory Band 2020-09-22T16:26:45.355200Z

ah ok

dpsutton 2020-09-22T16:27:06.355600Z

https://clojure.org/guides/repl/navigating_namespaces has some good information

Ory Band 2020-09-22T16:27:26.355900Z

besides using protocols and records, is there another way to implement updaters?

phronmophobic 2020-09-22T16:27:45.356300Z

one of the biggest improvements is just spending time thinking about what the stable interface should be

2020-09-22T16:28:06.356900Z

I tried that but it's unable to resolve any of the definitions

dpsutton 2020-09-22T16:28:48.357600Z

can you paste what you've tried?

2020-09-22T16:29:11.358Z

Lambda Island (https://lambdaisland.com/guides/clojure-repls/clojure-repls) says the REPL that appears when you do this isn't the socket REPL per se, and you need to connect to it via netcat (`nc localhost 50505`)

2020-09-22T16:29:15.358400Z

Hold on

phronmophobic 2020-09-22T16:29:16.358500Z

as for updaters, the answer is always "it depends" 😛 . you can also use multimethods

Ory Band 2020-09-22T16:29:28.358700Z

can you show an example?

Ory Band 2020-09-22T16:30:16.359Z

my main concern is hiding the internal fields e.g. :field-a :field-b

2020-09-22T16:30:22.359300Z

user=&gt; (require 'grok.db.core)
nil
user=&gt; database-uri
Syntax error compiling at (REPL:0:0).
Unable to resolve symbol: database-uri in this context

dpsutton 2020-09-22T16:30:58.360300Z

'(require '[grok.db.core :as grok]) (grok/database-uri)`

phronmophobic 2020-09-22T16:30:58.360400Z

i guess my question for you is what happens if you want to change :field-a?

Ory Band 2020-09-22T16:31:20.361Z

nothing complex. just set the field. i just don't think it's a good idea to expose the field name, the key

dpsutton 2020-09-22T16:31:21.361200Z

or you can read about in-ns from the link above to know how to get into a namespace

phronmophobic 2020-09-22T16:31:22.361300Z

do you change

(defn my-model-&gt;b [m] (:b m)
to
(defn my-model-&gt;c [m] (:c m)

phronmophobic 2020-09-22T16:31:55.361600Z

or do you still have

(defn my-model-&gt;b [m] (:c m)
where the field and function name don't match

Ory Band 2020-09-22T16:32:04.362Z

even if i do, just because refactoring tools exist to easily rename functions rather than fields (keys), it makes the job much easier

2020-09-22T16:33:02.362700Z

Mmm, still not resolving. I also tried doing (in-ns 'grok.db.core) to no avail

phronmophobic 2020-09-22T16:33:11.363Z

I think of the big insights I picked up from learning is using clojure is to focus on the data model and work with that directly

2020-09-22T16:33:30.363400Z

It's weird, because there was one time it worked and I could access everything from the REPL

phronmophobic 2020-09-22T16:33:32.363600Z

rather than focusing on the getters/setters and other functions around the data

phronmophobic 2020-09-22T16:34:15.364Z

there's a bunch of reasons why that mindset works well in the long run compared to the getter/setter approach

Ory Band 2020-09-22T16:34:35.364200Z

ok, so what do i do if i rename a field for example? i have to rename it everywhere i use it, either read or set it

Ory Band 2020-09-22T16:34:43.364600Z

it also breaks backward compatability

2020-09-22T16:35:13.365700Z

When I reconnected via netcat, I suddenly had the same errors appearing again

dpsutton 2020-09-22T16:35:18.365800Z

if you've already run (ns grok.db.core) you've created a new empty namespace. restart your repl and then do (require '[grok.db.core]) (in-ns grok-db.core)

phronmophobic 2020-09-22T16:35:38.366Z

that's a good question

Ory Band 2020-09-22T16:35:51.366400Z

i understand your point. but this is the cost

2020-09-22T16:36:20.366700Z

All right, lemme try that

phronmophobic 2020-09-22T16:36:21.366800Z

it's the data that really sticks around, not the functions around it

dpsutton 2020-09-22T16:36:30.367100Z

and if you're just using a bare repl, don't netcat just type in the repl. no need for another process involved

phronmophobic 2020-09-22T16:36:43.367200Z

so if you write your data to disk or send it over the network, it's the data that is being sent

phronmophobic 2020-09-22T16:37:06.367400Z

so it's really worth trying to get your data model right and thinking about how it might evolve over time

phronmophobic 2020-09-22T16:37:35.367600Z

writing getters/setters is convenient in the short term, but it's just kicking the can down the road

Ory Band 2020-09-22T16:38:08.367800Z

> so it's really worth trying to get your data model right and thinking about how it might evolve over time i can't plan 2 years ahead. no matter how hard i try

phronmophobic 2020-09-22T16:39:18.368Z

> plans are useless, but planning is essential

phronmophobic 2020-09-22T16:39:51.368200Z

right, you can't predict the future, but that doesn't mean that the decisions you make about your data model won't affect you down the road

Ory Band 2020-09-22T16:40:09.368400Z

ok, i get it

phronmophobic 2020-09-22T16:40:27.368600Z

the OO claim is that getters/setters will insulate you from these changes, but in my experience, that is a false promise

phronmophobic 2020-09-22T16:43:30.369Z

there's several reasons against using getters/setters, but as far as isolating you from changing field names, it only helps you within your program. if you write your data to disk or send it over the network, you still have to figure out how to deal with changing field names. further, if you package your code as a library, changing a field name can still break library users when they upgrade

Ory Band 2020-09-22T16:46:28.369200Z

i understand. thank you

Ory Band 2020-09-22T16:46:37.369400Z

also for being patient :)

phronmophobic 2020-09-22T16:47:02.370100Z

these are all good questions

phronmophobic 2020-09-22T16:47:17.370300Z

i don't think i'm explaining this alternate approach all that well

Ory Band 2020-09-22T16:48:43.370600Z

btw, this guy says something along your lines: https://clojureverse.org/t/do-you-encapsulate-domain-entity-maps/6147/5

1👍
Nazar Trut 2020-09-22T16:50:44.372300Z

(defn find-not-in-kb
  [expr kb]
  (filter #(and (sequential? %) (= (first %) 'if) (= (second %) (nth expr 1))) kb)
  )
Currently, this looks for an "if" in the first of a sequence and then an random element in the second of sequence, how would i change my function so it looks for the third element of the sequence instead of the second ?

Harley Waagmeester 2020-09-22T17:06:01.373800Z

this method used to work for me --> (.getURLs (java.lang.ClassLoader/getSystemClassLoader))but now i get the runtime error --> java.lang.IllegalArgumentException: No matching field found: getURLs

Harley Waagmeester 2020-09-22T17:06:40.374300Z

i wonder what has changed and how i might repair this ?

ghadi 2020-09-22T17:08:52.374700Z

the default classloader after java 9 is no longer a URLClassLoader

ghadi 2020-09-22T17:10:31.376Z

(it was never promised to be a URLClassLoader in 8, but you could cast it)

Harley Waagmeester 2020-09-22T17:11:15.376600Z

ahh, migration is a good search term

seancorfield 2020-09-22T17:17:24.376700Z

@suren You get used to reading (Java) stack traces but they can be daunting at first.

Harley Waagmeester 2020-09-22T17:36:54.377500Z

marvellous, a simple (seq (java.lang.System/getProperty "java.class.path")) is working for me in the repl

2020-09-22T17:41:35.377600Z

Note that (java.lang.System/getProperty "java.class.path") returns a string, so (seqing it will return a sequence of characters (which may or may not be what you’re after).

2020-09-22T17:46:23.377900Z

(clojure.string/split (java.lang.System/getProperty "java.class.path") #":") might be more like what you’re after?

Harley Waagmeester 2020-09-22T17:48:34.378100Z

yes, i am always surprised at how i mess up data presentation, and splitting on the ";" is just right

2020-09-22T17:49:15.378300Z

No worries - I do it all the time too. I find the REPL and (clojure.pprint/pprint) are my best friends for helping to visualise data. 😉

2020-09-22T17:49:56.378500Z

There are also fancier GUI-based tools that can graphically present Clojure data structures (often integrated with the REPL), but I’ve found (pprint) to be more than enough in most cases.

Marcus 2020-09-22T17:55:22.380100Z

hi! Functions that have side effects are often named with an ! in the end. How do you name functions that use resources that can change. E.g. querying a db or using other non-immutable resources.

seancorfield 2020-09-22T18:01:15.382Z

I tend to just use a normal function name, even though the function could return different results over time. Otherwise everything up your call chain to -main would need to be annotated with something indicating "side effects" 🙂

1
seancorfield 2020-09-22T18:03:54.383700Z

The original intent of ! was to indicate functions that would not be safe in a "transaction" (STM) but its use isn't very consistent -- some folks use it on anything that causes side effects, some only for functions that cause external side effects, some even for functions that call down to such functions.

seancorfield 2020-09-22T18:05:43.385400Z

In clojure.java.jdbc and next.jdbc, functions that are intended to be "just" queries have regular names (`query`, get-by-id, find-by-keys, plan -- since plan itself doesn't actually do anything, only when you reduce it do you get the side-effects), as opposed to update!, insert!, execute!

seancorfield 2020-09-22T18:07:16.387Z

(`next.jdbc` is a bit more aggressive about this because execute! and execute-one! can be used for reading the DB as well as updating it, unlike c.j.j -- and there are next.jdbc.plan/select! and next.jdbc.plan/select-one! which are helpers that wrap plan with reductions, even if they are queries)

alexmiller 2020-09-22T18:21:51.387900Z

if you wanted to write a new faq entry for https://github.com/clojure/clojure-site/blob/master/content/guides/faq.adoc, this is a frequent question - I think you've said the critical bits here

Nazar Trut 2020-09-22T18:22:22.388700Z

How would i filter a seqeunce like this "((if a b) a (if b c)) and only return a "a"

Nazar Trut 2020-09-22T18:22:23.388900Z

?

Nazar Trut 2020-09-22T18:23:22.389300Z

So i want my filter function to find a specific element in my sequence

alexmiller 2020-09-22T18:23:47.389500Z

you might want some instead

Nazar Trut 2020-09-22T18:24:49.389700Z

Ok thank you!!

seancorfield 2020-09-22T18:26:39.390Z

Under Reader and Syntax?

seancorfield 2020-09-22T18:28:04.390200Z

"What do ? and ! mean in function names?" perhaps?

seancorfield 2020-09-22T18:28:21.390400Z

(following "What does an _ mean...")

alexmiller 2020-09-22T18:38:52.390700Z

That sounds like a good place for it

2020-09-22T20:29:12.391200Z

is point free style dead?

2020-09-22T20:29:28.391600Z

what's the latest on point free style. does the community like it?

2020-09-22T20:31:24.392800Z

if all my functions were unary, i'd feel unconflicted about composing them. but since they aren't, it makes point free style more... involved.

ghadi 2020-09-22T20:31:39.393Z

point free was never alive in clojure

2020-09-22T20:33:28.393300Z

10-4, @ghadi

seancorfield 2020-09-22T20:35:15.393900Z

Despite the presence of comp and partial, the use of anonymous functions is generally preferred.

1🙌
seancorfield 2020-09-22T20:35:39.394400Z

That being said, comp is common when composing transformations as part of a transducer invocation.

jsn 2020-09-22T20:46:20.394900Z

Can you say more about it? I think I heard that partial is "better" because it doesn't create a closure. Is #() considered more idiomatic?

seancorfield 2020-09-22T20:49:01.395100Z

Rich is on record (several times) as saying he considers #() to be more idiomatic than partial.

jsn 2020-09-22T20:50:05.395300Z

Huh. Thanks, I felt so too initially, actually.

seancorfield 2020-09-22T20:50:22.395500Z

partial definitely does create closures -- look at its source.

jsn 2020-09-22T20:51:19.395700Z

Well, I figured it must create at least something equivalent (callable and holding the arguments)

seancorfield 2020-09-22T20:52:45.395900Z

partial creates a closure with multiple arities -- since it doesn't know how many arguments your function takes (so it doesn't know how many are still "missing") so if you have a function that takes four or more arguments, you're going to be running via a variadic function using apply.

seancorfield 2020-09-22T20:56:35.396100Z

If you use #(f arg1 %1 %2) then you get a 2-arity closure and your function f will be directly invoked with three arguments -- so you'll get IDE/linter support on the call of f in terms of validating the arity, and you'll also get runtime arity checking when that closure is called. With partial, you lose any IDE/linter support at the call site when writing the code, and you also defer the runtime arity checking (since you go through another call layer between the source code and the actual call site of your function -- and it might go through apply, pushing the arity checking a little further away from what you wrote. All of which makes errors harder to spot and debug.

jsn 2020-09-22T20:57:19.396300Z

Yes, got it, thank you!

seancorfield 2020-09-22T20:58:14.396500Z

FWIW, when I got started with Clojure a decade ago, I was very enamored with comp and partial... but I hardly ever use them nowadays.

seancorfield 2020-09-22T20:58:24.396700Z

(I do still like juxt tho')

jsn 2020-09-22T20:59:02.396900Z

So partial seems to be a good fit for when you really want varargs, though? Like (partial + 10)

jsn 2020-09-22T21:00:04.397100Z

#(apply + 10 %&amp;) would be the alternative, I guess

seancorfield 2020-09-22T21:12:51.397300Z

But in what contexts would you actually want a varargs (anonymous) function? I can't recall ever using %&amp; in a decade of production Clojure and I think I've only used partial on a variadic function "by accident", i.e., when the actual call(s) of that expression are known arities.

seancorfield 2020-09-22T21:16:17.397500Z

Yeah, just grepped my code here: no instances of %&amp; in well over 100k lines of Clojure (including all my OSS work). I have just a handful of (partial apply =) in test code (but that is not variadic, it takes a single collection argument -- so I could (and should) just use #(apply = %)

seancorfield 2020-09-22T21:17:39.397700Z

Across our entire codebase, we have just over 100 calls to partial in just under 60 files.

jsn 2020-09-22T21:17:48.397900Z

Yeah, I can probably come up with an artificial example, but I guess you're right, it's not common at all

seancorfield 2020-09-22T21:18:26.398100Z

By contrast, we have well over 500 anonymous functions with #(..) across nearly 200 files.

jsn 2020-09-22T21:19:18.398300Z

(I actually had to check the docs before even mentioning %&amp; for its existence; never used it in real life)

jsn 2020-09-22T21:20:30.398500Z

Thank you!

Josef Richter 2020-09-22T21:39:16.398700Z

@baptiste-from-paris I need to send a post request to the backend every time the user changes the state of the checkbox. it’s a todo list. I am actually not submitting a whole form. it’s a “form” that includes just two elements: one input type=hidden + the checkbox (previously a button type=submit).