Fork me on GitHub
#beginners
<
2020-09-22
>
Nazar Trut00:09:02

(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 Trut00:09:07

does anyone know why?

Nazar Trut00:09:33

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

seancorfield00:09:37

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.

seancorfield00:09:27

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

Nazar Trut00:09:50

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

João Galrito00:09:18

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

João Galrito00:09:39

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

Nazar Trut00:09:41

its because kb can be both

Nazar Trut00:09:35

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 Trut00:09:55

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

Nazar Trut00:09:01

it would work

João Galrito00:09:21

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

seancorfield00:09:33

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.

seancorfield00:09:11

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

Nazar Trut00:09:15

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

seancorfield00:09:26

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 Trut00:09:12

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

seancorfield00:09:21

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

seancorfield00:09:40

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

seancorfield00:09:17

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

Nazar Trut00:09:48

(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 Trut00:09:17

It is just passed through my infer function

João Galrito00:09:50

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

João Galrito00:09:11

but you're not creating kb there

João Galrito00:09:15

you're just passing it

Nazar Trut00:09:52

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

João Galrito00:09:23

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

João Galrito00:09:40

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

João Galrito00:09:09

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

Nazar Trut00:09:20

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

Nazar Trut00:09:25

Ok ill try that

João Galrito00:09:36

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

Nazar Trut00:09:01

how would i wrap everything in a list

João Galrito00:09:20

i gave you the code earlier

misto quente01:09:46

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?

seancorfield01:09:02

Your namespace is requiring guestbook.core -- but is ClojureScript and guestbook.core is Clojure, based on the extensions.

seancorfield01:09:30

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.

seancorfield01:09:45

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

👍 3
seancorfield01:09:37

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

misto quente01:09:51

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

seancorfield01:09:47

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

seancorfield01:09:49

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

misto quente01:09:44

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 quente02:09:31

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

seancorfield05:09:23

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

João Galrito01:09:21

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

João Galrito01:09:55

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

hiredman01:09:24

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

hiredman01:09:03

Look at tree traversals in functional languages

hiredman01:09:29

General you use recursive function calls

hiredman01:09:09

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 Galrito01:09:44

i'm traversing the tree alright

João Galrito01:09:11

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

João Galrito01:09:47

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

dpsutton01:09:49

its a threadsafe mutable container.

hiredman01:09:25

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

João Galrito01:09:11

yeah, that's what i was thinking

João Galrito01:09:04

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

João Galrito01:09:15

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

João Galrito01:09:21

and call the multimethod inside that

hiredman01:09:46

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

João Galrito01:09:40

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

dpsutton01:09:56

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 Galrito01:09:51

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

João Galrito02:09:15

I think atom is what I want here

Nazar Trut03:09:52

(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 Trut03:09:42

@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 Galrito03:09:32

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

Nazar Trut03:09:55

because this function only looks for ifs

Nazar Trut03:09:31

it should look for ifs

João Galrito03:09:16

then you need to add another condition to your filter predicate

João Galrito03:09:31

to check if what you're reading is a list

João Galrito03:09:18

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

Nazar Trut03:09:25

I figured it out

seancorfield03:09:32

sequential? is probably the predicate you want here.

Nazar Trut03:09:37

nvm i havent got it

Nazar Trut03:09:05

haha this is hard

Nazar Trut03:09:13

Problems only lead to more problems

João Galrito03:09:14

@seancorfield any particular reason? If just working with lists

seancorfield03:09:34

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

João Galrito03:09:36

(instead of list? for example)

seancorfield03:09:51

@joao.galrito

user=> (list? ())
true
user=> (list? (range 5))
false
user=> (list? (map inc (range 5)))
false
user=> (sequential? ())
true
user=> (sequential? (range 5))
true
user=> (sequential? (map inc (range 5)))
true
user=>

João Galrito03:09:23

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

seancorfield03:09:24

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

João Galrito03:09:33

he said kb is coming from the REPL

seancorfield03:09:59

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

João Galrito03:09:19

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

seancorfield03:09:41

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

suren06:09:26

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?

suren06:09:28

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

suren06:09:58

Thanks I will try it.

seancorfield06:09:09

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

seancorfield06:09:05

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

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

seancorfield06:09:57

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

seancorfield06:09:06

Since (str nil) is ""

suren06:09:24

Thanks @seancorfield it worked like a charm

suren06:09:02

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

dpsutton06:09:11

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

dpsutton06:09:40

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

seancorfield06:09:27

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

suren06:09:16

Just error out.

seancorfield06:09:32

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

seancorfield06:09:47

I suspect .createNewFile would error out if it already exists

seancorfield06:09:36

Ah, no, it would just return false

seancorfield06:09:48

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

suren06:09:16

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

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

seancorfield06:09:40

user=> (.createNewFile ( "/tmp/foo"))
true ; file did not exist, this created it
user=> (.createNewFile ( "/tmp/foo"))
false ; file existed so it was not touched
user=> 

seancorfield06:09:01

io/file creates a .File object so you'll have to read the Java API docs for that.

seancorfield06:09:13

But it's basically an abstraction for a file.

seancorfield06:09:40

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.

suren06:09:59

hmm got it

suren06:09:32

For my use case I will use (.createNewFile

seancorfield06:09:56

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 🙂

seancorfield06:09:14

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

suren06:09:19

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

suren06:09:05

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

seancorfield17:09:24

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

suren05:09:47

Yeah too much noise in the stack traces.

v3ga07:09:00

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

Ilari Tuominen08:09:05

@U06FM9QN9 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 Tuominen08:09:40

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

v3ga09:09:15

Yeah. I’ve touched on both but I believe I’m going to focus on reitit. It’s a little friendlier to beginners while at the same time being more than performant.

Risetto08:09:54

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

Risetto08:09:45

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

v3ga09:09:15

Yeah. I’ve touched on both but I believe I’m going to focus on reitit. It’s a little friendlier to beginners while at the same time being more than performant.

Risetto10:09:01

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

delaguardo10:09:00

like if? (if x 1 0)

Risetto10:09:56

Yea, but the other way around 🙂

Risetto10:09:09

Oh nvm 😄

kimim11:09:28

({true 1 false 0} x)

👍 3
kimim08:09:09

Maybe you want a default result:

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

delaguardo08:09:36

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

kimim08:09:47

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

michele mendel12:09:53

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

João Galrito14:09:53

does doseq not realize a lazy seq?

João Galrito14:09:27

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

João Galrito14:09:48

thought that was what doseq was for

João Galrito14:09:38

I see there's also run!

teodorlu14:09:08

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 Galrito14:09:46

I tried doseq but nothing happened

teodorlu14:09:50

What did you try? Here's an example:

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

João Galrito14:09:24

(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 Galrito14:09:42

where chunks is

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

teodorlu06:09:37

I don't see anything that's wrong. Do you observe your (println "Inserted " (count operations)) output?

teodorlu06:09:31

> anyway in my case I can take advantage of parallelism so ended up using `pmap` and `dorun` Looks like you worked it out!

Alex Miller (Clojure team)14:09:35

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

João Galrito14:09:04

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

João Galrito14:09:38

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

Ory Band15:09:26

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->id [m] (:id m)
(defn my-model->a [m] (:a m)
(defn my-model->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?

phronmophobic15:09:33

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 (->Circle 2))
(perimeter (->Square 4))

Ory Band15:09:28

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

phronmophobic15:09:35

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

Ory Band16:09:26

ok. what about accessors? the setters

Ory Band16:09:31

how to set stuff.

phronmophobic16:09:27

(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] (->Circle (* r s))))

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

(area (->Circle 2))
(perimeter (->Square 4))
(scale (->Square 2) 2) 

phronmophobic16:09:27

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 Band16:09:32

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 Band16:09:26

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

phronmophobic16:09:45

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

phronmophobic16:09:16

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

Ory Band16:09:28

can you show an example?

Ory Band16:09:16

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

phronmophobic16:09:58

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

Ory Band16:09:20

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

phronmophobic16:09:22

do you change

(defn my-model->b [m] (:b m)
to
(defn my-model->c [m] (:c m)

phronmophobic16:09:55

or do you still have

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

Ory Band16:09:04

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

phronmophobic16:09:11

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

phronmophobic16:09:32

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

phronmophobic16:09:15

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

Ory Band16:09:35

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 Band16:09:43

it also breaks backward compatability

phronmophobic16:09:38

that's a good question

Ory Band16:09:51

i understand your point. but this is the cost

phronmophobic16:09:21

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

phronmophobic16:09:43

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

phronmophobic16:09:06

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

phronmophobic16:09:35

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

Ory Band16:09:08

> 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

phronmophobic16:09:18

> plans are useless, but planning is essential

phronmophobic16:09:51

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 Band16:09:09

ok, i get it

phronmophobic16:09:27

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

phronmophobic16:09:30

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 Band16:09:28

i understand. thank you

Ory Band16:09:37

also for being patient :)

phronmophobic16:09:02

these are all good questions

phronmophobic16:09:17

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

Ory Band10:09:29

I want to reiterate on this topic after watching this presentation: https://lispcast.com/you-are-in-a-maze-of-deeply-nested-maps-all-alike-talk/ This guy is practically saying the opposite of what we talked about above: There is a lot of sense in using constructors and accessors (getters, setters), which in practice encapsulate your data. It is worth the extra code investment, because you gain the ability to decouple implementation details from depending code areas. It also makes Clojure tell you when you call a function which got renamed, in contrast to changing a key name. There's more to that than I said, especially when it's a good idea to use these abstractions. I recommend you watch this talk.

phronmophobic16:09:08

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.

Michael Stokley16:09:02

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"

Michael Stokley16:09:28

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

phronmophobic17:09:00

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

👍 3
phronmophobic17:09:39

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

👍 3
Clark Urzo16:09:47

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

Clark Urzo16:09:56

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}"]}}}

Clark Urzo16:09:43

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

Clark Urzo16:09:32

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

dpsutton16:09:58

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

Clark Urzo16:09:06

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

dpsutton16:09:48

can you paste what you've tried?

Clark Urzo16:09:11

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

Clark Urzo16:09:22

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

dpsutton16:09:58

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

dpsutton16:09:21

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

Clark Urzo16:09:02

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

Clark Urzo16:09:30

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

Clark Urzo16:09:13

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

dpsutton16:09:18

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)

Clark Urzo02:09:18

I think I got it now. I was spoiled by lein in that I didn't have to require namespaces there before switching to them: I can just do it directly with ns

Clark Urzo02:09:36

Your solution definitely works. Thanks!

Clark Urzo02:09:14

One thing that bothers me though is why in-ns works when you pass in a nonexistent namespace

seancorfield02:09:02

Per the docstring: "Sets ns to the namespace named by the symbol, creating it if needed."

seancorfield02:09:28

So it will create a completely empty namespace if it doesn't already exist. It won't have any clojure.core functions referred in.

Clark Urzo02:09:46

Is there a usecase for that? I'd hate to be derailed by an errant typo

Clark Urzo02:09:07

Ahh, hmm so how does it differ from ns then?

seancorfield02:09:28

ns does a whole bunch of stuff: creates the ns if needed, refer in clojure.core and a bunch of other stuff, requires specified namespaces, imports specified classes, etc.

seancorfield02:09:40

in-ns is very low-level.

Clark Urzo02:09:41

Wait, you meant *ns* right?

Clark Urzo02:09:45

I see, I see

seancorfield02:09:31

I mean the ns macro. Not *ns*. The latter is a dynamic Var.

user=> (in-ns 'foo.bar)
#object[clojure.lang.Namespace 0xdf5f5c0 "foo.bar"]
foo.bar=> (ns-publics *ns*)
Syntax error compiling at (REPL:1:1).
Unable to resolve symbol: ns-publics in this context
foo.bar=> (clojure.core/ns-publics *ns*)
Syntax error compiling at (REPL:1:1).
Unable to resolve symbol: *ns* in this context
foo.bar=> (clojure.core/ns-publics clojure.core/*ns*)
{}
foo.bar=> (clojure.core/ns-refers clojure.core/*ns*)
{}
foo.bar=> (clojure.core/ns-aliases clojure.core/*ns*)
{}
foo.bar=> 

Clark Urzo02:09:12

BTW I saw your post earlier this year on whether or not switching to deps.edn is a good idea and it's the main reason I'm trying to do it now. Thanks!

seancorfield02:09:23

See how I have to specify clojure.core/ in front of various symbol names? Normally -- when you use the ns macro -- those are automatically referred in.

👍 3
Clark Urzo02:09:33

I mean in your first reply, since *ns* appears as ns

seancorfield02:09:11

Oh, this? Per the docstring: "Sets `*ns*` to the namespace named by the symbol, creating it if needed." Yeah, I just pasted in the docstring and forgot it would auto-format that!

clojure-spin 3
Clark Urzo02:09:08

Just checked ClojureDocs, I should probably check it more often. In any case, thanks for the detailed responses! Really starting to get the hang of this thing.

seancorfield02:09:30

Don't forget you can use doc and source right there in your REPL.

Clark Urzo02:09:15

Oh yeah, that's really helpful. Should probably get used to them.

seancorfield02:09:06

I have doc bound to ctrl-; d and source bound to ctrl-; S in my editor so they are easily available 🙂

seancorfield02:09:58

(aside from helping beginners with REPL sessions like the above, I never actually type into my REPL -- I always type into a source file, sometimes in a comment form, and then eval code directly into the REPL from the editor)

clj 3
Clark Urzo02:09:01

Ahh, since I’m moving around so much and can’t bring a decent keyboard with me all the time, I’m kind of stuck with vim. But hearing your suggestion has led me to this https://thoughtbot.com/blog/writing-clojure-in-vim so I’m going to try to replicate your setup. Let’s see how it goes.

Clark Urzo16:09:20

All right, lemme try that

dpsutton16:09:30

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

Nazar Trut16:09:44

(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 Waagmeester17:09:01

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 Waagmeester17:09:40

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

ghadi17:09:52

the default classloader after java 9 is no longer a URLClassLoader

ghadi17:09:31

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

Harley Waagmeester17:09:15

ahh, migration is a good search term

Harley Waagmeester17:09:54

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

deactivateduser17:09:35

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

deactivateduser17:09:23

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

Harley Waagmeester17:09:34

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

deactivateduser17:09:15

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

deactivateduser17:09:56

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.

Marcus17:09:22

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.

seancorfield18:09:15

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" 🙂

3
seancorfield18:09:54

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.

Alex Miller (Clojure team)18:09:51

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

seancorfield18:09:39

Under Reader and Syntax?

seancorfield18:09:04

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

seancorfield18:09:21

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

Alex Miller (Clojure team)18:09:52

That sounds like a good place for it

seancorfield18:09:43

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!

seancorfield18:09:16

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

Nazar Trut18:09:22

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

Nazar Trut18:09:22

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

Alex Miller (Clojure team)18:09:47

you might want some instead

Nazar Trut18:09:49

Ok thank you!!

Michael Stokley20:09:12

is point free style dead?

Michael Stokley20:09:28

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

Michael Stokley20:09:24

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.

ghadi20:09:39

point free was never alive in clojure

seancorfield20:09:15

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

🙌 3
jsn20:09:20

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?

seancorfield20:09:01

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

jsn20:09:05

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

seancorfield20:09:22

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

jsn20:09:19

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

seancorfield20:09:45

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.

seancorfield20:09:35

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.

jsn20:09:19

Yes, got it, thank you!

seancorfield20:09:14

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.

seancorfield20:09:24

(I do still like juxt tho')

jsn20:09:02

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

jsn21:09:04

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

seancorfield21:09:51

But in what contexts would you actually want a varargs (anonymous) function? I can't recall ever using %& 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.

seancorfield21:09:17

Yeah, just grepped my code here: no instances of %& 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 = %)

seancorfield21:09:39

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

jsn21:09:48

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

seancorfield21:09:26

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

jsn21:09:18

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

jsn21:09:30

Thank you!

seancorfield20:09:39

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

Clark Urzo02:09:18

I think I got it now. I was spoiled by lein in that I didn't have to require namespaces there before switching to them: I can just do it directly with ns

Clark Urzo02:09:01

Ahh, since I’m moving around so much and can’t bring a decent keyboard with me all the time, I’m kind of stuck with vim. But hearing your suggestion has led me to this https://thoughtbot.com/blog/writing-clojure-in-vim so I’m going to try to replicate your setup. Let’s see how it goes.

Ory Band10:09:29
replied to a thread: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. :grimacing: Wdyt?

I want to reiterate on this topic after watching this presentation: https://lispcast.com/you-are-in-a-maze-of-deeply-nested-maps-all-alike-talk/ This guy is practically saying the opposite of what we talked about above: There is a lot of sense in using constructors and accessors (getters, setters), which in practice encapsulate your data. It is worth the extra code investment, because you gain the ability to decouple implementation details from depending code areas. It also makes Clojure tell you when you call a function which got renamed, in contrast to changing a key name. There's more to that than I said, especially when it's a good idea to use these abstractions. I recommend you watch this talk.