Fork me on GitHub
#beginners
<
2019-06-06
>
didibus03:06:41

@matt.henley Hum... it's not always wrong, but in your case it is. I'd say you want to map each components to a namespace, think component diagram in UML.

didibus03:06:02

Also, your tasklist is a poor's man data-structure and a remnant of you trying to do OOP. It doesn't need to be encapsulated behind an interface.

didibus03:06:38

(def tasklists {})
You're done

didibus03:06:38

You could spec it to help people understand its structure and work with it more effectively if you really wanted.

didibus03:06:36

For your tasks, you could move them to be more like a state map with transitions. Like adding a status key: {:status #{:open :completed}}

didibus03:06:03

So now you don't need a completed? isOpen? IsPrending? Etc. You can just call :status as a function

didibus03:06:50

You can also now have a multi-spec over status

didibus03:06:16

To specify all of your data invariant at any given state

didibus03:06:40

And I would personally go all in with the state machine. And I'd create a transitions map which lists valid transition from a status to others like:

(def transitions
  {:open [:completed :pending]
   :completed []
   :pending [:completed]})

didibus03:06:03

Now I would accept transition commands, and I would multi-spec them over the :status key

didibus03:06:15

(defmulti t (fn[task new-state] [(:status task) new-state]))
(defmethod t [:open :close] [task new-state]
 (-> task
  (assoc :status new-state)
  (assoc :date-completed (. js/Date now))))
 (t {:status :open} :close)
;> {:status :close,
     :date-completed 1559792294514}

didibus03:06:56

Finally, I'd move all that in a component which would be my only namespace for it all. Maybe I name it taskscheduler.cljs

didibus03:06:52

And you'd think of some cool interface for that component.

didibus03:06:59

Make these public

didibus03:06:28

Internally, it would make new tasks by calling your create and adding them to the tasklists namespace global Var. It would check for the status by just checking the :status key on a task. It would transition status with the multi-method, throwing an exception if no valid transition from/to exists. It could return the valid transitions from the transitions map for a given task if you need to list them out in the UI for example.

didibus03:06:48

I realize now maybe your tasks were more for a todo app 😋. I was definitely thinking scheduled jobs in my head.

didibus04:06:54

The state machine might be overkill for that.

didibus04:06:38

Then I'd just shove it all in say a todo.clj namespace. And I'd make it all simpler like:

(def lists (atom {}))
(defn new-list [name]
  ...)
(defn add-task [list-name item]
  ...)
(defn close-task [list-name task-idx]
  ...)
Etc.

Ahmed Hassan08:06:37

What are options for browser automation from ClojureScript?

shidima08:06:08

You could look at puppeteer

jayeldoubleu13:06:31

I am trying to use websockets with sente. When I try to curl the endpoint i get this error message:

Client's Ring request doesn't have a client id. Does your server have the necessary keyword Ring middleware (`wrap-params` & `wrap-keyword-params`)?*
and a stack trace in the server that says the same thing. I am just following the example outlined on the project's homepage, https://github.com/ptaoussanis/sente#getting-started. this gist has the server and the curl command and output: https://gist.github.com/jldoubleu/70dbe2eb6d31ae8a5c36730d748e0fc0

jayeldoubleu13:06:22

i guess curling isn't really the problem. i just realized in the gist i wasn't passing client-id query param

jayeldoubleu13:06:25

the actually problem that i was trying to reduce to the simplest case is that the clojurescript client is getting erroring out 403 when trying to establish the connection

jayeldoubleu13:06:39

403 Bad CSRF token. which tells me what is wrong, but i am not really sure how to resolve it. For this example i do not really care about csrf

lepistane13:06:54

@justin.williams.1181 lucky you 😄 i spent a lot on this few months ago 😄 and this is the answer https://github.com/ptaoussanis/sente/issues/339#issuecomment-478187728

jayeldoubleu13:06:09

ok, i set the csrf-token-fn to nil in the call to make-channel-socket! and that seems to have resolved my issue for the test case

lepistane13:06:09

yeah that's about it if you ever need a token just look at sente example it will give you a hint on how to do it. (render home.html on backend with csrf and then use that value for communication)

jayeldoubleu13:06:19

@lepistane yeah thanks that is what i figured out to work around. which example is that?

Tom Fenton14:06:33

Hi! I'm trying to import classes from inside an external jar file but I'm having zero success - feel like I'm missing something obvious (it's my first foray into interop), would anyone be able to give me some pointers? 🙂

jumar14:06:38

What did you try and how did it fail?

Tom Fenton14:06:58

I've built an app around SAP's Java Connector ("sapjco3.jar"). I've tried two ways to import classes from it, both of which work fine in the REPL and during uberjar build, but cause issues at runtime. The first is adding a :resource-paths entry to my projects.clj, which I assume is the correct method but causes the JVM to fail to start ("Java Virtual Machine Launcher: A Java Exception has occurred.") The second is extracting the contents of the jar to my resources directory, which works(with limitations.

Tom Fenton14:06:10

Since I can't repackage SAP's code due to licensing restrictions, I assume I need to keep sapjco3.jar outside of my uberjar and reference it at runtime?

jumar14:06:52

If so, why not just add it as a regular dependency?

jumar14:06:40

Otherwise you should probably install it into your own maven repo or at least the local repository

Tom Fenton15:06:18

Ok, I've tried installing the jars to my local repository, but SAP's jars check the file name at runtime to prevent repackaging; since mvn install renames them to "sapjco3-version.jar", and doesn't seem to respect -DstripVersion=true, I'm not sure if this route will work

Tom Fenton15:06:45

(JCo initialization failed with java.lang.ExceptionInInitializerError: Illegal JCo archive "sapjco3-3.0.19.jar". It is not allowed to rename or repackage the original archive "sapjco3.jar".)

Tom Fenton16:06:29

(On reflection, even if there's a technical workaround I think adding it as a dependency would count as repackaging SAP's code and therefore isn't viable)

noisesmith16:06:58

clojure is a java library, use the java classpath config to add the jar when starting clojure

noisesmith16:06:31

you don't need to use maven or include it in an uberjar - just ensure that it's in a reliably findable place on disk, and add it to your classpath on startup

Tom Fenton08:06:19

noisesmith, that makes sense, but I can't import classes even though (.exists (File. "sapjco3.jar")) returns true, which would suggest that it's already on the classpath. I'm not sure how I'd declare this explicitly in projects.clj due to this change: https://github.com/technomancy/leiningen/wiki/Repeatability#free-floating-jars

jumar08:06:45

Perhaps first try with -cp on command line...

jumar08:06:00

And then to ensure repeatability of your build process you probably need to install it somewhere. You don't have private nexus or alternative?

noisesmith17:06:26

> (.exists (File. "sapjco3.jar")) returns true, which would suggest that it's already on the classpath this is not how classpath works, the current directory usually isn't on classpath

noisesmith17:06:45

as @U06BE1L6T says you use the -cp arg to java to set classpath

noisesmith17:06:57

you can use lein install or an equivalent mvn command to make an arbitrary jar usable via maven, or hard code any path you like if you want to make a special case

Tom Fenton12:06:36

Hi jumar, noisesmith, will experiment with -cp and see where I get to. I guess I need to configure my IDE to add the jars to the classpath on REPL launch, then run my uberjar'd application from e.g. a batch file that references both the uberjar and the SAP jar? Thanks for all your advice so far!

noisesmith16:06:48

that's likely the simplest option, yeah

RafaMedina17:06:54

Hi all, a very beginner question, how does a function return something which manipulates a lazy map? a println works inside the func, but if I call it from outside it doesn't show anything

noisesmith17:06:23

@comparalf I'm not sure if I'm reading that correctly, but if you call map inside the function you either need to realize the elements needed before you exit, or return the lazy-seq so something else can realize it

noisesmith17:06:05

if you don't realize an element, and don't return the seq containing it, the containing sequence gets garbage collected without realizing it

noisesmith17:06:47

user=> ,((fn [] (map println (range 1000)) nil))
nil

RafaMedina18:06:25

Uhm.. So, I think the question is now, how do you convert the map to something no-lazy? materialize it..

didibus18:06:33

You want to use doall

noisesmith18:06:56

or use run! or doseq instead of map if you weren't using the data

didibus18:06:12

That said, it's not deep. So if you have lazy things of lazy things, you need to call doall on all of them.

didibus18:06:16

Ya, also mapv, reduce and loop can be used instead for eager processing which return a useful value

didibus18:06:27

And when you feel like you've mastered all that, transducers can also be used for eager processing. But that's more complicated so maybe don't start there.

RafaMedina18:06:59

Ok ok, I think I got the basic idea, I'll look for those functions, thanks to both

Suni Masuno18:06:57

So I'm new-ish to clojure (at least at scale) and picking up someone else's codebase. If I had a bunch of "what's the prettiest way to write this?" questions which channel should I direct that at?

Suni Masuno18:06:20

I consumed the style guide, but I'm trying for better than the minimum. ^_^

alexmiller18:06:33

here or #clojure are fine

👍 4
Suni Masuno18:06:56

Thanks! ^_^ So which is preferred... (get-in data [0 :someKey]) (:someKey (first data)) or is there some clever difference between them?

hiredman18:06:30

I would not use get-in on a seq

Suni Masuno18:06:50

So the second version is more data agnostic?

seancorfield18:06:47

I'd use either (:some-key (first data)) or (-> data first :some-key) depending on what the surrounding code looked like.

seancorfield18:06:35

(I agree with @hiredman that using get/`get-in` to navigate sequences is something I would not use)

Suni Masuno18:06:59

If it helps this is a vector of... is map the right word in clojure for {:a 2}?. And a vector is still officially a seq right?

Suni Masuno18:06:24

Honestly the data in here looks like json with {}/[] nestings

hiredman18:06:34

vectors are not seqs

Suni Masuno18:06:52

List => seq and vector as peers then?

Suni Masuno18:06:05

>.< Sorry, clearly got a lot left to learn

hiredman18:06:32

a seq is like an iterator

hiredman18:06:50

when you get an iterator over a collection, that iterator is not the collection

hiredman18:06:03

so you can get a seq from a vector, but a vector is not a seq

Suni Masuno18:06:55

So if in my earlier example I preface with...

(def data [{someKey 12} {someKey 22}])
Does that change the correct answer?

Suni Masuno18:06:21

assuming I want 12

yuhan19:06:43

I think the issue is that using get/get-in restricts your function to only working with vectors instead of sequences in general

Suni Masuno19:06:27

Making the other way more data agnostic?

yuhan19:06:38

yeah, it could come back to bite you later on if you pass eg. the result of a map or filter to it

Suni Masuno19:06:04

That makes sense. If the pathing down starts to get pretty long is there a better option, or should I vec=>get-in to get the same behavior?

yuhan19:06:49

the -> threading macro is a pretty readable option for that

yuhan19:06:55

you could also look into destructuring the value directly

Suni Masuno19:06:08

Ok, I can see how to do it that way. I'm coming from js/ramda https://ramdajs.com/0.19.1/docs/#path and I suppose that warped my perception here a bit. Still learning to think more clojure-y

zane20:06:04

I don't think there's anything wrong with using get-in here if you know that all the data structures you're going to be traversing are associative, and both maps and vectors are.

Suni Masuno20:06:05

Ohhh, new concept for me here, what exactly does associative mean? (linked article would be fine if more convenient)

zane20:06:08

In fact, personally I would prefer get-in in that case.

zane20:06:24

Let me see if I can dig up some reference material for you.

Suni Masuno20:06:17

🙇 thank you!

zane20:06:32

Not finding anything great, unfortunately. I'll tuck my own explanation in this thread.

zane20:06:40

@suni_masuno Clojure data structures are said to be 'associative' if the implement the clojure.lang.Associative interface.

zane20:06:57

Roughly speaking being 'associative' means that they associate keys with values.

zane20:06:21

In a map the associative relationships are straightforward: Each key is associated with its corresponding value.

zane20:06:10

Vectors are also considered associative. They map their indices to their values.

zane20:06:26

You can test if a data structure is associative with clojure.core/associative?:

(associative? [])
true
(associative? {})
true
(associative? #{})
false

Suni Masuno20:06:57

That... is a super helpful answer. ^_^ You should write one of those tiny super helpful medium posts I'm always coming across with just that.

zane20:06:18

Maybe so! 🙂 Glad you found it helpful.

zane20:06:34

As others were pointing out above, many functions in clojure.core that consume collections will output sequences regardless of the type of data that was provided as input. For example:

(associative? [1 2 3])
true
(map inc [1 2 3])
(2 3 4)
(associative? (map inc [1 2 3]))
false

Ahmed Hassan15:06:56

@U050CT4HR so, only Lists and Sets are Seqable?

zane21:06:57

@UCMNZLJ93 Nope! Being associative and being seqable are not mutually exclusive. If you seq a vector you get a sequence of the vector's values. If you seq a map you get a sequence of key/value pairs.

zane21:06:29

All the built-in collections are seqable.

Ahmed Hassan21:06:11

So, when you make a collection seqable does it retains associative properties?

zane05:06:56

@UCMNZLJ93 If you're talking about seq, seq doesn't "make a collection seqable", it turns the collection into a sequence. Sequences are not associative.

Ahmed Hassan05:06:04

@U050CT4HR what is difference between seq and seqable?

zane05:06:43

@UCMNZLJ93 seq is a function in Clojure core. Seqable is a Java interface that collections implement.

Suni Masuno20:06:15

Hoping out of the thread, get or get-in are introducing a requirement of associative, while arguably being a bit simpler/more readable depending on taste?

zane20:06:51

Sounds right to me.

zane20:06:45

The thing I like about get-in in this case relative to, say, -> is that it's easier to scan (makes the intent more explicit), particularly as gets long. For instance, if I see (get-in x …) even before looking at I already know that all we're doing is accessing data within x. By contrast, if I see (-> x …) I need to read each item in to understand whether we're accessing data, transforming data, triggering side effects, or some combination of all three.

zane20:06:31

This is an instance of the more general principle of using the smallest / most limited tool for the job.

zane20:06:42

But as you say, it's definitely a question of taste. 🙂

alexmiller20:06:26

get-in is great when someone else hands you the path

8
Suni Masuno20:06:39

@alexmiller Yeah, that's more the pattern I'm used to in Ramda too where we're handing around paths near as much as data (ok, like 20% as much, but still) That said the much greater diversity of data representations in clojure makes get-in significantly less safe than path is in JS

Suni Masuno20:06:53

A point I didn't know before this channel!

Suni Masuno20:06:47

🙇 thank you!

4
seancorfield21:06:49

@suni_masuno Something else to be aware of

user=> (get-in [{:some-key 12} {:some-key 22}] [2 :some-key])
nil
user=> (-> [{:some-key 12} {:some-key 22}] (nth 2) :some-key)
Execution error (IndexOutOfBoundsException) at user/eval37656 (REPL:4).
null
user=> 
get/`get-in` will yield nil when the element can't be found but nth will throw an exception. However:
user=> (get-in (list {:some-key 12} {:some-key 22}) [2 :some-key])
nil
user=> (get-in (list {:some-key 12} {:some-key 22}) [1 :some-key])
nil
user=> (-> (list {:some-key 12} {:some-key 22}) (nth 2) :some-key)
Execution error (IndexOutOfBoundsException) at user/eval37662 (REPL:7).
null
user=> (-> (list {:some-key 12} {:some-key 22}) (nth 1) :some-key)
22
user=> 
Note how get-in produces nil even for an index that is in bounds here -- because a list is not associative -- but you can use nth on a list although it will be O(n) -- linear.

12
papachan22:06:09

Not sure if this is the correct channel. regarding a clojurescript question: I started a new project with figwheel and it was running correctly my reload function, css changes make my project reload as well. But when i started with figwheel-main tools and lein trampoline i just loose theses features. Probably i may didnt understand my config with my profiles settings. Someone here can help me?

RafaMedina22:06:09

@papachan do you have the figwheel-main.edn file?

papachan22:06:26

{:target-dir "resources"
 :watch-dirs ["src"]
 :open-url ""}
`

RafaMedina22:06:25

I did the same thing, migrate from to figwheel to figwheel-main, I remember to change my aliases in project.clj

:aliases {"fig"       ["run" "-m" "figwheel.main"]
            "fig:build" ["run" "-m" "figwheel.main" "-b" "dev" "-r"]
            "fig:min"   ["run" "-m" "figwheel.main" "-O" "advanced" "-bo" "dev"]
            "fig:test"  ["run" "-m" "figwheel.main" "-co" "test.cljs.edn" "-m" condo.test-runner]

RafaMedina22:06:38

I've just tested de fig:build.. but it works so I think at least, it'll run

RafaMedina22:06:22

And I added [com.bhauman/figwheel-main "0.1.9"] for dependencies in dev profile

papachan22:06:43

here there are my aliases settings:

:aliases { "fig" ["trampoline" "run" "-m" "figwheel.main"]
             "fig:dev" ["trampoline" "run" "-m" "figwheel.main" "-b" "dev" "-r"]}

RafaMedina22:06:44

I had not never seen trampoline, you could try give a shot without it, using the same parameters that fig:build

papachan22:06:39

hmmm same behaviour

RafaMedina23:06:49

Uhm, if the problem is with the css files, in the project.clj

:resource-paths ["resources"]

papachan23:06:24

with figwheel i had this, i suppose its not valid anymore when i move to figwheel-main

papachan23:06:31

:figwheel {
             :css-dirs ["resources/public/css"] ;; watch and update CSS

papachan23:06:19

Oh i can add this to dev.cljs.edn file

RafaMedina23:06:05

^{:watch-dirs ["test/cljs" "src/cljs" "src/cljc"]
  :css-dirs ["resources/public/css"]
  :auto-testing true
  :log-level :error}

RafaMedina23:06:09

I hope so.. haahahah

RafaMedina23:06:27

That's my dev.cljs.edn

papachan23:06:40

yeah it worked

papachan23:06:50

my last question is how i can use again this reload function: :figwheel {:on-jsload "clojurescript-app.core/on-js-reload"

noisesmith23:06:59

wouldn't that just be adding an :on-jsload key to the dev.cljs.edn?

noisesmith23:06:14

maybe it's too much to hope it would be that simple

papachan23:06:05

{:watch-dirs ...,
   :css-dirs ...,
   :auto-testing ...,
   :on-jsload ...}
   ^^^^^^^^^^

should be one of ...
`

papachan23:06:26

it didnt accept it